bpo-26389: Allow passing an exception object in the traceback module (GH-22610)

The format_exception(), format_exception_only(), and
print_exception() functions can now take an exception object as a positional-only argument.

Co-Authored-By: Matthias Bussonnier <bussonniermatthias@gmail.com>
This commit is contained in:
Zackery Spytz 2020-11-05 15:18:44 -07:00 committed by GitHub
parent dc42af8fd1
commit 91e93794d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 97 additions and 24 deletions

View File

@ -36,7 +36,8 @@ The module defines the following functions:
Added negative *limit* support. Added negative *limit* support.
.. function:: print_exception(etype, value, tb, limit=None, file=None, chain=True) .. function:: print_exception(exc, /[, value, tb], limit=None, \
file=None, chain=True)
Print exception information and stack trace entries from traceback object Print exception information and stack trace entries from traceback object
*tb* to *file*. This differs from :func:`print_tb` in the following *tb* to *file*. This differs from :func:`print_tb` in the following
@ -45,7 +46,7 @@ The module defines the following functions:
* if *tb* is not ``None``, it prints a header ``Traceback (most recent * if *tb* is not ``None``, it prints a header ``Traceback (most recent
call last):`` call last):``
* it prints the exception *etype* and *value* after the stack trace * it prints the exception type and *value* after the stack trace
.. index:: single: ^ (caret); marker .. index:: single: ^ (caret); marker
@ -53,6 +54,10 @@ The module defines the following functions:
format, it prints the line where the syntax error occurred with a caret format, it prints the line where the syntax error occurred with a caret
indicating the approximate position of the error. indicating the approximate position of the error.
Since Python 3.10, instead of passing *value* and *tb*, an exception object
can be passed as the first argument. If *value* and *tb* are provided, the
first argument is ignored in order to provide backwards compatibility.
The optional *limit* argument has the same meaning as for :func:`print_tb`. The optional *limit* argument has the same meaning as for :func:`print_tb`.
If *chain* is true (the default), then chained exceptions (the If *chain* is true (the default), then chained exceptions (the
:attr:`__cause__` or :attr:`__context__` attributes of the exception) will be :attr:`__cause__` or :attr:`__context__` attributes of the exception) will be
@ -62,6 +67,10 @@ The module defines the following functions:
.. versionchanged:: 3.5 .. versionchanged:: 3.5
The *etype* argument is ignored and inferred from the type of *value*. The *etype* argument is ignored and inferred from the type of *value*.
.. versionchanged:: 3.10
The *etype* parameter has been renamed to *exc* and is now
positional-only.
.. function:: print_exc(limit=None, file=None, chain=True) .. function:: print_exc(limit=None, file=None, chain=True)
@ -121,18 +130,26 @@ The module defines the following functions:
text line is not ``None``. text line is not ``None``.
.. function:: format_exception_only(etype, value) .. function:: format_exception_only(exc, /[, value])
Format the exception part of a traceback. The arguments are the exception Format the exception part of a traceback using an exception value such as
type and value such as given by ``sys.last_type`` and ``sys.last_value``. given by ``sys.last_value``. The return value is a list of strings, each
The return value is a list of strings, each ending in a newline. Normally, ending in a newline. Normally, the list contains a single string; however,
the list contains a single string; however, for :exc:`SyntaxError` for :exc:`SyntaxError` exceptions, it contains several lines that (when
exceptions, it contains several lines that (when printed) display detailed printed) display detailed information about where the syntax error occurred.
information about where the syntax error occurred. The message indicating The message indicating which exception occurred is the always last string in
which exception occurred is the always last string in the list. the list.
Since Python 3.10, instead of passing *value*, an exception object
can be passed as the first argument. If *value* is provided, the first
argument is ignored in order to provide backwards compatibility.
.. versionchanged:: 3.10
The *etype* parameter has been renamed to *exc* and is now
positional-only.
.. function:: format_exception(etype, value, tb, limit=None, chain=True) .. function:: format_exception(exc, /[, value, tb], limit=None, chain=True)
Format a stack trace and the exception information. The arguments have the Format a stack trace and the exception information. The arguments have the
same meaning as the corresponding arguments to :func:`print_exception`. The same meaning as the corresponding arguments to :func:`print_exception`. The
@ -143,6 +160,10 @@ The module defines the following functions:
.. versionchanged:: 3.5 .. versionchanged:: 3.5
The *etype* argument is ignored and inferred from the type of *value*. The *etype* argument is ignored and inferred from the type of *value*.
.. versionchanged:: 3.10
This function's behavior and signature were modified to match
:func:`print_exception`.
.. function:: format_exc(limit=None, chain=True) .. function:: format_exc(limit=None, chain=True)

View File

@ -232,6 +232,15 @@ retrieve the functions set by :func:`threading.settrace` and
:func:`threading.setprofile` respectively. :func:`threading.setprofile` respectively.
(Contributed by Mario Corchero in :issue:`42251`.) (Contributed by Mario Corchero in :issue:`42251`.)
traceback
---------
The :func:`~traceback.format_exception`,
:func:`~traceback.format_exception_only`, and
:func:`~traceback.print_exception` functions can now take an exception object
as a positional-only argument.
(Contributed by Zackery Spytz and Matthias Bussonnier in :issue:`26389`.)
types types
----- -----
@ -328,6 +337,15 @@ This section lists previously described changes and other bugfixes
that may require changes to your code. that may require changes to your code.
Changes in the Python API
-------------------------
* The *etype* parameters of the :func:`~traceback.format_exception`,
:func:`~traceback.format_exception_only`, and
:func:`~traceback.print_exception` functions in the :mod:`traceback` module
have been renamed to *exc*.
(Contributed by Zackery Spytz and Matthias Bussonnier in :issue:`26389`.)
Build Changes Build Changes
============= =============

View File

@ -212,6 +212,26 @@ class TracebackCases(unittest.TestCase):
) )
self.assertEqual(output.getvalue(), "Exception: projector\n") self.assertEqual(output.getvalue(), "Exception: projector\n")
def test_print_exception_exc(self):
output = StringIO()
traceback.print_exception(Exception("projector"), file=output)
self.assertEqual(output.getvalue(), "Exception: projector\n")
def test_format_exception_exc(self):
e = Exception("projector")
output = traceback.format_exception(e)
self.assertEqual(output, ["Exception: projector\n"])
with self.assertRaisesRegex(ValueError, 'Both or neither'):
traceback.format_exception(e.__class__, e)
with self.assertRaisesRegex(ValueError, 'Both or neither'):
traceback.format_exception(e.__class__, tb=e.__traceback__)
with self.assertRaisesRegex(TypeError, 'positional-only'):
traceback.format_exception(exc=e)
def test_format_exception_only_exc(self):
output = traceback.format_exception_only(Exception("projector"))
self.assertEqual(output, ["Exception: projector\n"])
class TracebackFormatTests(unittest.TestCase): class TracebackFormatTests(unittest.TestCase):

View File

@ -84,7 +84,19 @@ _context_message = (
"another exception occurred:\n\n") "another exception occurred:\n\n")
def print_exception(etype, value, tb, limit=None, file=None, chain=True): _sentinel = object()
def _parse_value_tb(exc, value, tb):
if (value is _sentinel) != (tb is _sentinel):
raise ValueError("Both or neither of value and tb must be given")
if value is tb is _sentinel:
return exc, exc.__traceback__
return value, tb
def print_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \
file=None, chain=True):
"""Print exception up to 'limit' stack trace entries from 'tb' to 'file'. """Print exception up to 'limit' stack trace entries from 'tb' to 'file'.
This differs from print_tb() in the following ways: (1) if This differs from print_tb() in the following ways: (1) if
@ -95,9 +107,7 @@ def print_exception(etype, value, tb, limit=None, file=None, chain=True):
occurred with a caret on the next line indicating the approximate occurred with a caret on the next line indicating the approximate
position of the error. position of the error.
""" """
# format_exception has ignored etype for some time, and code such as cgitb value, tb = _parse_value_tb(exc, value, tb)
# passes in bogus values as a result. For compatibility with such code we
# ignore it here (rather than in the new TracebackException API).
if file is None: if file is None:
file = sys.stderr file = sys.stderr
for line in TracebackException( for line in TracebackException(
@ -105,7 +115,8 @@ def print_exception(etype, value, tb, limit=None, file=None, chain=True):
print(line, file=file, end="") print(line, file=file, end="")
def format_exception(etype, value, tb, limit=None, chain=True): def format_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \
chain=True):
"""Format a stack trace and the exception information. """Format a stack trace and the exception information.
The arguments have the same meaning as the corresponding arguments The arguments have the same meaning as the corresponding arguments
@ -114,19 +125,15 @@ def format_exception(etype, value, tb, limit=None, chain=True):
these lines are concatenated and printed, exactly the same text is these lines are concatenated and printed, exactly the same text is
printed as does print_exception(). printed as does print_exception().
""" """
# format_exception has ignored etype for some time, and code such as cgitb value, tb = _parse_value_tb(exc, value, tb)
# passes in bogus values as a result. For compatibility with such code we
# ignore it here (rather than in the new TracebackException API).
return list(TracebackException( return list(TracebackException(
type(value), value, tb, limit=limit).format(chain=chain)) type(value), value, tb, limit=limit).format(chain=chain))
def format_exception_only(etype, value): def format_exception_only(exc, /, value=_sentinel):
"""Format the exception part of a traceback. """Format the exception part of a traceback.
The arguments are the exception type and value such as given by The return value is a list of strings, each ending in a newline.
sys.last_type and sys.last_value. The return value is a list of
strings, each ending in a newline.
Normally, the list contains a single string; however, for Normally, the list contains a single string; however, for
SyntaxError exceptions, it contains several lines that (when SyntaxError exceptions, it contains several lines that (when
@ -137,7 +144,10 @@ def format_exception_only(etype, value):
string in the list. string in the list.
""" """
return list(TracebackException(etype, value, None).format_exception_only()) if value is _sentinel:
value = exc
return list(TracebackException(
type(value), value, None).format_exception_only())
# -- not official API but folk probably use these two functions. # -- not official API but folk probably use these two functions.

View File

@ -0,0 +1,4 @@
The :func:`traceback.format_exception`,
:func:`traceback.format_exception_only`, and
:func:`traceback.print_exception` functions can now take an exception object
as a positional-only argument.