refactor: consistent printline() across pose and quaternion classes - #200
refactor: consistent printline() across pose and quaternion classes#200petercorke wants to merge 2 commits into
Conversation
…Quaternion, UnitQuaternion types. In `base` now have separate functions to convert type to a single line string or to print it. Fixed problem where stdout redirection failed using `contextlib.redirect_stdout`.
Fixes discovered while packaging up old WIP work (commit 9dc38f9,
"Consistent operation of printline()...") for review. Three separate
bugs, all in that same unreleased WIP, none of it previously merged
or published:
1. trprint()/qprint() silently shifted `file` into the 2nd positional
argument slot (`def trprint(T, file=False, **kwargs)`), when the
original, published, external-user-facing signatures had `orient`/
`delim` there instead. `trprint(T, 'angvec')` or the library's own
documented `x.printline('angvec')` example put 'angvec' into `file`
instead of `orient`, crashing with
`AttributeError: 'str' object has no attribute 'write'`.
Fixed by restoring the full original explicit parameter order for
trprint/qprint (trprint2 was already fine here - its parameter
order happened to be unaffected).
2. trprint2() captured `label` as an explicit parameter but its body
never forwarded it to tr2str2() - `trprint2(T, label='T')` silently
dropped the label from the output.
3. All three (trprint/trprint2/qprint) stopped returning the compact
string entirely, only printing it - breaking any caller using the
deprecated-but-still-supported `file=None` -> return string instead
of printing usage, which several existing tests rely on
(test_transforms3d.py::test_print, test_transforms2d.py::test_print2).
While fixing (3): the `file=None` "give me a string instead" pattern
is a wart worth phasing out rather than perpetuating silently - each
of these now has a real *2str()/q2str() sibling that does that
directly. So `file=None` is kept working (still returns the string,
still doesn't print) but now consistently raises a DeprecationWarning
and says so explicitly in the docstring via `.. deprecated::`,
pointing at tr2str()/tr2str2()/q2str() as the replacement.
All three now follow one consistent shape: always build the string via
the *2str()/q2str() sibling, always return it, print only when
file is not None (file=False, the default, resolves to None passed to
print() so contextlib.redirect_stdout() sees the current sys.stdout at
call time rather than whatever was captured when the function was
defined - the actual bug this WIP originally set out to fix).
Verified: file=None still returns the correct string with a
DeprecationWarning and no printing; the default path both prints
(confirmed via contextlib.redirect_stdout) and returns the same
string; full test suite green (338 passed, 4 skipped);
black --check clean at 23.10.0.
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
|
Merge-order suggestion across the currently open PRs, based on a full file-overlap check across all of them. Nothing here is a conflict with anything already merged — these are parallel PRs sharing some source files with each other, so a rebase will likely be needed wherever paths cross, regardless of order. Recommended order:
Since this one lands last and touches the most shared surface, it'll very likely need a rebase once #194/#198/#199 are in — happy to do that rebase myself once they land, just flagging it now so it's not a surprise. |
What
Consistent
printline()behaviour acrossSO2/SE2/SO3/SE3/Quaternion/UnitQuaternion:Quaternion.printline()(previously only the pose classes had it).trprint/trprint2/qprintintodedicated
tr2str/tr2str2/q2str-style functions (the latter twoalready existed;
tr2str/tr2str2are new), so "give me the formattedstring" and "print the formatted string" are two separate, clearly-named
functions instead of one function trying to do both via a
fileflag.contextlib.redirect_stdout()didn't work with the old code, because
file=sys.stdoutas a defaultargument value is evaluated once at function-definition time and freezes
whatever
sys.stdoutwas then - it doesn't see a later redirect. Fixedby using a
file=Falsesentinel, resolved toNoneinside the functionbody (so
print()looks up the currentsys.stdoutat call time).Bugs found while packaging this up for review
This started as old WIP work from a local branch, never previously
reviewed or merged. Testing it before submission turned up three real bugs,
all fixed here:
trprint()/qprint()silently shiftedfileinto the 2nd positionalargument. The original, already-published signatures had
orient(
trprint) /delim(qprint) there.trprint(T, 'angvec')or thislibrary's own documented
x.printline('angvec')example put'angvec'intofileinstead, crashing withAttributeError: 'str' object has no attribute 'write'. Fixed by restoring the originalexplicit parameter order for both (
trprint2was unaffected - itsparameter order happened to already put
filein the same slot).trprint2()capturedlabelbut never forwarded it totr2str2()trprint2(T, label='T')silently dropped the label from the output.breaking the existing, still-supported
file=None→ "return the stringinstead of printing" usage that a couple of existing tests rely on
(
test_transforms3d.py::test_print,test_transforms2d.py::test_print2).While fixing (3), also formally deprecated that
file=Nonepattern ratherthan silently perpetuating it: each function now has a real
tr2str()/tr2str2()/q2str()sibling that returns the string directly,so
file=Nonestill works exactly as before (returns the string, doesn'tprint) but now raises a
DeprecationWarningand says so explicitly in thedocstring via
.. deprecated::, pointing at the replacement function.All three (
trprint/trprint2/qprint) now follow one consistent shape:build the string via the
*2str/q2strsibling, always return it, printonly when
file is not None.Testing
file=Nonestill returns the correct string with aDeprecationWarningand no printing, for all three functions.contextlib.redirect_stdout,confirming the original bug is actually fixed) and returns the same
string.
trprint(T, 'angvec'),x.printline('angvec')) work correctly again.black --checkclean at the pinned 23.10.0.Note on PR ordering
This PR and #198 (lazy Matplotlib import) both touch adjacent code in
spatialmath/base/transforms3d.py(right around_vec2s/_matplotlib_exists/trprint/trplot) - not a conflict with anythingalready merged, just two open PRs based on the same
upstream/masterpoint. Whichever lands first, the other will likely need a rebase to
resolve a real merge conflict before it can land.