diff --git a/Doc/library/traceback.rst b/Doc/library/traceback.rst index 796309c6cf0bb9..4506631effae45 100644 --- a/Doc/library/traceback.rst +++ b/Doc/library/traceback.rst @@ -341,6 +341,10 @@ capture data for later printing in a lightweight fashion. local variables in each :class:`FrameSummary` are captured as object representations. + .. versionchanged:: 3.12 + Exceptions raised from :func:`repr` on a local variable (when + *capture_locals* is ``True``) are no longer propagated to the caller. + .. classmethod:: from_list(a_list) Construct a :class:`StackSummary` object from a supplied list of diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 722c265a6a8a51..097e319d784cfc 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -2247,6 +2247,9 @@ def format_frame_summary(self, frame_summary): f' File "{__file__}", line {lno}, in f\n 1/0\n' ) +class Unrepresentable: + def __repr__(self) -> str: + raise Exception("Unrepresentable") class TestTracebackException(unittest.TestCase): @@ -2514,12 +2517,13 @@ def test_locals(self): linecache.updatecache('/foo.py', globals()) e = Exception("uh oh") c = test_code('/foo.py', 'method') - f = test_frame(c, globals(), {'something': 1, 'other': 'string'}) + f = test_frame(c, globals(), {'something': 1, 'other': 'string', 'unrepresentable': Unrepresentable()}) tb = test_tb(f, 6, None, 0) exc = traceback.TracebackException( Exception, e, tb, capture_locals=True) self.assertEqual( - exc.stack[0].locals, {'something': '1', 'other': "'string'"}) + exc.stack[0].locals, + {'something': '1', 'other': "'string'", 'unrepresentable': ''}) def test_no_locals(self): linecache.updatecache('/foo.py', globals()) diff --git a/Lib/traceback.py b/Lib/traceback.py index 3afe49d1d8a0e6..1086686b2f06c9 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -279,7 +279,8 @@ def __init__(self, filename, lineno, name, *, lookup_line=True, self._line = line if lookup_line: self.line - self.locals = {k: repr(v) for k, v in locals.items()} if locals else None + self.locals = {k: _safe_string(v, 'local', func=repr) + for k, v in locals.items()} if locals else None self.end_lineno = end_lineno self.colno = colno self.end_colno = end_colno diff --git a/Misc/ACKS b/Misc/ACKS index b6340414cf7012..32475f874c36db 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1590,6 +1590,7 @@ Ed Schouten Scott Schram Robin Schreiber Chad J. Schroeder +Simon-Martin Schroeder Christian Schubert Sam Schulenburg Andreas Schwab diff --git a/Misc/NEWS.d/next/Library/2022-07-08-17-49-12.gh-issue-87822.F9dzkf.rst b/Misc/NEWS.d/next/Library/2022-07-08-17-49-12.gh-issue-87822.F9dzkf.rst new file mode 100644 index 00000000000000..7b27f5df45ba20 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-07-08-17-49-12.gh-issue-87822.F9dzkf.rst @@ -0,0 +1 @@ +When called with ``capture_locals=True``, the :mod:`traceback` module functions swallow exceptions raised from calls to ``repr()`` on local variables of frames. This is in order to prioritize the original exception over rendering errors. An indication of the failure is printed in place of the missing value. (Patch by Simon-Martin Schroeder).