Skip to content

Commit 8d18e5d

Browse files
committed
fix(base): fix bugs found in the printline/trprint/qprint rewrite
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.
1 parent bb1f6f7 commit 8d18e5d

3 files changed

Lines changed: 51 additions & 19 deletions

File tree

spatialmath/base/quaternions.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1152,7 +1152,12 @@ def q2str(
11521152
return template.format(q[0], delim[0], q[1], q[2], q[3], delim[1])
11531153

11541154

1155-
def qprint(q: Union[ArrayLike4, ArrayLike4], file=False, **kwargs) -> None:
1155+
def qprint(
1156+
q: Union[ArrayLike4, ArrayLike4],
1157+
delim: Optional[Tuple[str, str]] = ("<", ">"),
1158+
fmt: Optional[str] = "{: .4f}",
1159+
file: Optional[TextIO] = False,
1160+
) -> str:
11561161
"""
11571162
Compact single-line display of a quaternion
11581163
@@ -1182,17 +1187,26 @@ def qprint(q: Union[ArrayLike4, ArrayLike4], file=False, **kwargs) -> None:
11821187
>>> q = qrand() # a unit quaternion
11831188
>>> qprint(q, delim=('<<', '>>'))
11841189
1190+
.. deprecated:: 1.1.15
1191+
``file=None`` to get the string back without printing is
1192+
deprecated - call :func:`~q2str` directly instead.
1193+
11851194
:seealso: :meth:`q2str`
11861195
"""
11871196
q = smb.getvector(q, 4)
1197+
s = q2str(q, delim=delim, fmt=fmt)
11881198
if file is None:
11891199
warnings.warn(
11901200
"Usage: qprint(..., file=None) -> str is deprecated, use q2str() instead",
11911201
DeprecationWarning,
11921202
)
1193-
if file is False:
1194-
file = None # defaults to stdout
1195-
print(q2str(q, **kwargs), file=file)
1203+
else:
1204+
# file=False (the default) resolves to None here so print() looks
1205+
# up the *current* sys.stdout at call time, not whatever it was
1206+
# when this function was defined - that's what makes
1207+
# contextlib.redirect_stdout() work.
1208+
print(s, file=None if file is False else file)
1209+
return s
11961210

11971211

11981212
if __name__ == "__main__": # pragma: no cover

spatialmath/base/transforms2d.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -963,7 +963,6 @@ def trinterp2(start, end, s, shortest: bool = True):
963963
def tr2str2(
964964
T: Union[SO2Array, SE2Array],
965965
label: str = "",
966-
file: TextIO = None,
967966
fmt: str = "{:.3g}",
968967
unit: str = "deg",
969968
) -> str:
@@ -974,8 +973,6 @@ def tr2str2(
974973
:type T: ndarray(3,3) or ndarray(2,2)
975974
:param label: text label to put at start of line
976975
:type label: str
977-
:param file: file to write formatted string to
978-
:type file: file object
979976
:param fmt: conversion format for each number
980977
:type fmt: str
981978
:param unit: angular units: 'rad' [default], or 'deg'
@@ -1101,19 +1098,25 @@ def trprint2(
11011098
- For tabular data set ``fmt`` to a fixed width format such as
11021099
``fmt='{:.3g}'``
11031100
1104-
.. versionchanged:: 1.1.15
1105-
To create a string use :func:`~tr2str2` instead of ``trprint2(...file=None)``
1101+
.. deprecated:: 1.1.15
1102+
``file=None`` to get the string back without printing is
1103+
deprecated - call :func:`~tr2str2` directly instead.
11061104
11071105
:seealso: :func:`~tr2str2` :func:`~trprint`
11081106
"""
1107+
s = tr2str2(T, label=label, **kwargs)
11091108
if file is None:
11101109
warnings.warn(
11111110
"Usage: trprint2(..., file=None) -> str is deprecated, use tr2str2() instead",
11121111
DeprecationWarning,
11131112
)
1114-
if file is False:
1115-
file = None # defaults to stdout
1116-
print(tr2str2(T, **kwargs), file=file)
1113+
else:
1114+
# file=False (the default) resolves to None here so print() looks
1115+
# up the *current* sys.stdout at call time, not whatever it was
1116+
# when this function was defined - that's what makes
1117+
# contextlib.redirect_stdout() work.
1118+
print(s, file=None if file is False else file)
1119+
return s
11171120

11181121

11191122
def points2tr2(p1: NDArray, p2: NDArray) -> SE2Array:

spatialmath/base/transforms3d.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2785,7 +2785,6 @@ def tr2str(
27852785
T: Union[SO3Array, SE3Array],
27862786
orient: str = "rpy/zyx",
27872787
label: str = "",
2788-
file: TextIO = None,
27892788
fmt: str = "{:.3g}",
27902789
degsym: bool = True,
27912790
unit: str = "deg",
@@ -2904,7 +2903,15 @@ def _vec2s(fmt, v):
29042903
return ", ".join([fmt.format(x) for x in v])
29052904

29062905

2907-
def trprint(T: Union[SO3Array, SE3Array], file=False, **kwargs) -> str:
2906+
def trprint(
2907+
T: Union[SO3Array, SE3Array],
2908+
orient: str = "rpy/zyx",
2909+
label: str = "",
2910+
file: TextIO = False,
2911+
fmt: str = "{:.3g}",
2912+
degsym: bool = True,
2913+
unit: str = "deg",
2914+
) -> str:
29082915
"""
29092916
Compact single-line display of SO(3) or SE(3) matrices
29102917
@@ -2962,20 +2969,28 @@ def trprint(T: Union[SO3Array, SE3Array], file=False, **kwargs) -> str:
29622969
- For tabular data set ``fmt`` to a fixed width format such as
29632970
``fmt='{:.3g}'``
29642971
2965-
.. versionchanged:: 1.1.15
2966-
To create a string use :func:`~tr2str` instead of ``trprint(...file=None)``
2972+
.. deprecated:: 1.1.15
2973+
``file=None`` to get the string back without printing is
2974+
deprecated - call :func:`~tr2str` directly instead.
29672975
29682976
:seealso: :func:`~tr2str` :func:`~spatialmath.base.transforms2d.trprint2` :func:`~tr2eul` :func:`~tr2rpy` :func:`~tr2angvec`
29692977
:SymPy: not supported
29702978
"""
2979+
s = tr2str(T, orient=orient, label=label, fmt=fmt, degsym=degsym, unit=unit)
29712980
if file is None:
2981+
# deprecated: return the string without printing, matching the
2982+
# original `if file: print(...)` falsy-skip behaviour
29722983
warnings.warn(
29732984
"Usage: trprint(..., file=None) -> str is deprecated, use tr2str() instead",
29742985
DeprecationWarning,
29752986
)
2976-
if file is False:
2977-
file = None # defaults to stdout
2978-
print(tr2str(T, **kwargs), file=file)
2987+
else:
2988+
# file=False (the default) resolves to None here so print() looks
2989+
# up the *current* sys.stdout at call time, not whatever it was
2990+
# when this function was defined - that's what makes
2991+
# contextlib.redirect_stdout() work.
2992+
print(s, file=None if file is False else file)
2993+
return s
29792994

29802995

29812996
try:

0 commit comments

Comments
 (0)