Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion Doc/library/traceback.rst
Original file line number Diff line number Diff line change
Expand Up @@ -212,11 +212,16 @@ The module also defines the following classes:
:class:`TracebackException` objects are created from actual exceptions to
capture data for later printing in a lightweight fashion.

.. class:: TracebackException(exc_type, exc_value, exc_traceback, *, limit=None, lookup_lines=True, capture_locals=False)
.. class:: TracebackException(exc_type, exc_value, exc_traceback, *, limit=None, lookup_lines=True, capture_locals=False, compact=False)

Capture an exception for later rendering. *limit*, *lookup_lines* and
*capture_locals* are as for the :class:`StackSummary` class.

If *compact* is true, only data that is required by :class:`TracebackException`'s
``format`` method is saved in the class attributes. In particular, the
``__context__`` field is calculated only if ``__cause__`` is ``None`` and
``__suppress_context__`` is false.

Note that when locals are captured, they are also shown in the traceback.

.. attribute:: __cause__
Expand Down Expand Up @@ -294,6 +299,9 @@ capture data for later printing in a lightweight fashion.
The message indicating which exception occurred is always the last
string in the output.

.. versionchanged:: 3.10
Added the *compact* parameter.


:class:`StackSummary` Objects
-----------------------------
Expand Down
40 changes: 40 additions & 0 deletions Lib/test/test_traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -1173,6 +1173,46 @@ def f():
self.assertIn(
"RecursionError: maximum recursion depth exceeded", res[-1])

def test_compact_with_cause(self):
try:
try:
1/0
finally:
cause = Exception("cause")
raise Exception("uh oh") from cause
except Exception:
exc_info = sys.exc_info()
exc = traceback.TracebackException(*exc_info, compact=True)
expected_stack = traceback.StackSummary.extract(
traceback.walk_tb(exc_info[2]))
exc_cause = traceback.TracebackException(Exception, cause, None)
self.assertEqual(exc_cause, exc.__cause__)
self.assertEqual(None, exc.__context__)
self.assertEqual(True, exc.__suppress_context__)
self.assertEqual(expected_stack, exc.stack)
self.assertEqual(exc_info[0], exc.exc_type)
self.assertEqual(str(exc_info[1]), str(exc))

def test_compact_no_cause(self):
try:
try:
1/0
finally:
exc_info_context = sys.exc_info()
exc_context = traceback.TracebackException(*exc_info_context)
raise Exception("uh oh")
except Exception:
exc_info = sys.exc_info()
exc = traceback.TracebackException(*exc_info, compact=True)
expected_stack = traceback.StackSummary.extract(
traceback.walk_tb(exc_info[2]))
self.assertEqual(None, exc.__cause__)
self.assertEqual(exc_context, exc.__context__)
self.assertEqual(False, exc.__suppress_context__)
self.assertEqual(expected_stack, exc.stack)
self.assertEqual(exc_info[0], exc.exc_type)
self.assertEqual(str(exc_info[1]), str(exc))

def test_no_refs_to_exception_and_traceback_objects(self):
try:
1/0
Expand Down
25 changes: 16 additions & 9 deletions Lib/traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ def print_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \
value, tb = _parse_value_tb(exc, value, tb)
if file is None:
file = sys.stderr
for line in TracebackException(
type(value), value, tb, limit=limit).format(chain=chain):
te = TracebackException(type(value), value, tb, limit=limit, compact=True)
for line in te.format(chain=chain):
print(line, file=file, end="")


Expand All @@ -126,8 +126,8 @@ def format_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \
printed as does print_exception().
"""
value, tb = _parse_value_tb(exc, value, tb)
return list(TracebackException(
type(value), value, tb, limit=limit).format(chain=chain))
te = TracebackException(type(value), value, tb, limit=limit, compact=True)
return list(te.format(chain=chain))


def format_exception_only(exc, /, value=_sentinel):
Expand All @@ -146,8 +146,8 @@ def format_exception_only(exc, /, value=_sentinel):
"""
if value is _sentinel:
value = exc
return list(TracebackException(
type(value), value, None).format_exception_only())
te = TracebackException(type(value), value, None, compact=True)
return list(te.format_exception_only())


# -- not official API but folk probably use these two functions.
Expand Down Expand Up @@ -476,7 +476,8 @@ class TracebackException:
"""

def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None,
lookup_lines=True, capture_locals=False, _seen=None):
lookup_lines=True, capture_locals=False, compact=False,
_seen=None):
# NB: we need to accept exc_traceback, exc_value, exc_traceback to
# permit backwards compat with the existing API, otherwise we
# need stub thunk objects just to glue it together.
Expand All @@ -485,6 +486,7 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None,
if _seen is None:
_seen = set()
_seen.add(id(exc_value))

# TODO: locals.
self.stack = StackSummary.extract(
walk_tb(exc_traceback), limit=limit, lookup_lines=lookup_lines,
Expand All @@ -504,7 +506,7 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None,
if lookup_lines:
self._load_lines()
self.__suppress_context__ = \
exc_value.__suppress_context__ if exc_value else False
exc_value.__suppress_context__ if exc_value is not None else False

# Convert __cause__ and __context__ to `TracebackExceptions`s, use a
# queue to avoid recursion (only the top-level call gets _seen == None)
Expand All @@ -524,8 +526,13 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None,
_seen=_seen)
else:
cause = None

if compact:
need_context = cause is None and not e.__suppress_context__
else:
need_context = True
if (e and e.__context__ is not None
and id(e.__context__) not in _seen):
and need_context and id(e.__context__) not in _seen):
context = TracebackException(
type(e.__context__),
e.__context__,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Added the ``compact`` parameter to the constructor of
:class:`traceback.TracebackException` to reduce time and memory
for use cases that only need to call :func:`TracebackException.format`
and :func:`TracebackException.format_exception_only`.