Skip to content

Commit 91e9379

Browse files
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 <[email protected]>
1 parent dc42af8 commit 91e9379

File tree

5 files changed

+97
-24
lines changed

5 files changed

+97
-24
lines changed

Doc/library/traceback.rst

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ The module defines the following functions:
3636
Added negative *limit* support.
3737

3838

39-
.. function:: print_exception(etype, value, tb, limit=None, file=None, chain=True)
39+
.. function:: print_exception(exc, /[, value, tb], limit=None, \
40+
file=None, chain=True)
4041

4142
Print exception information and stack trace entries from traceback object
4243
*tb* to *file*. This differs from :func:`print_tb` in the following
@@ -45,14 +46,18 @@ The module defines the following functions:
4546
* if *tb* is not ``None``, it prints a header ``Traceback (most recent
4647
call last):``
4748

48-
* it prints the exception *etype* and *value* after the stack trace
49+
* it prints the exception type and *value* after the stack trace
4950

5051
.. index:: single: ^ (caret); marker
5152

5253
* if *type(value)* is :exc:`SyntaxError` and *value* has the appropriate
5354
format, it prints the line where the syntax error occurred with a caret
5455
indicating the approximate position of the error.
5556

57+
Since Python 3.10, instead of passing *value* and *tb*, an exception object
58+
can be passed as the first argument. If *value* and *tb* are provided, the
59+
first argument is ignored in order to provide backwards compatibility.
60+
5661
The optional *limit* argument has the same meaning as for :func:`print_tb`.
5762
If *chain* is true (the default), then chained exceptions (the
5863
:attr:`__cause__` or :attr:`__context__` attributes of the exception) will be
@@ -62,6 +67,10 @@ The module defines the following functions:
6267
.. versionchanged:: 3.5
6368
The *etype* argument is ignored and inferred from the type of *value*.
6469

70+
.. versionchanged:: 3.10
71+
The *etype* parameter has been renamed to *exc* and is now
72+
positional-only.
73+
6574

6675
.. function:: print_exc(limit=None, file=None, chain=True)
6776

@@ -121,18 +130,26 @@ The module defines the following functions:
121130
text line is not ``None``.
122131

123132

124-
.. function:: format_exception_only(etype, value)
133+
.. function:: format_exception_only(exc, /[, value])
134+
135+
Format the exception part of a traceback using an exception value such as
136+
given by ``sys.last_value``. The return value is a list of strings, each
137+
ending in a newline. Normally, the list contains a single string; however,
138+
for :exc:`SyntaxError` exceptions, it contains several lines that (when
139+
printed) display detailed information about where the syntax error occurred.
140+
The message indicating which exception occurred is the always last string in
141+
the list.
125142

126-
Format the exception part of a traceback. The arguments are the exception
127-
type and value such as given by ``sys.last_type`` and ``sys.last_value``.
128-
The return value is a list of strings, each ending in a newline. Normally,
129-
the list contains a single string; however, for :exc:`SyntaxError`
130-
exceptions, it contains several lines that (when printed) display detailed
131-
information about where the syntax error occurred. The message indicating
132-
which exception occurred is the always last string in the list.
143+
Since Python 3.10, instead of passing *value*, an exception object
144+
can be passed as the first argument. If *value* is provided, the first
145+
argument is ignored in order to provide backwards compatibility.
133146

147+
.. versionchanged:: 3.10
148+
The *etype* parameter has been renamed to *exc* and is now
149+
positional-only.
134150

135-
.. function:: format_exception(etype, value, tb, limit=None, chain=True)
151+
152+
.. function:: format_exception(exc, /[, value, tb], limit=None, chain=True)
136153

137154
Format a stack trace and the exception information. The arguments have the
138155
same meaning as the corresponding arguments to :func:`print_exception`. The
@@ -143,6 +160,10 @@ The module defines the following functions:
143160
.. versionchanged:: 3.5
144161
The *etype* argument is ignored and inferred from the type of *value*.
145162

163+
.. versionchanged:: 3.10
164+
This function's behavior and signature were modified to match
165+
:func:`print_exception`.
166+
146167

147168
.. function:: format_exc(limit=None, chain=True)
148169

Doc/whatsnew/3.10.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,15 @@ retrieve the functions set by :func:`threading.settrace` and
232232
:func:`threading.setprofile` respectively.
233233
(Contributed by Mario Corchero in :issue:`42251`.)
234234

235+
traceback
236+
---------
237+
238+
The :func:`~traceback.format_exception`,
239+
:func:`~traceback.format_exception_only`, and
240+
:func:`~traceback.print_exception` functions can now take an exception object
241+
as a positional-only argument.
242+
(Contributed by Zackery Spytz and Matthias Bussonnier in :issue:`26389`.)
243+
235244
types
236245
-----
237246

@@ -328,6 +337,15 @@ This section lists previously described changes and other bugfixes
328337
that may require changes to your code.
329338

330339

340+
Changes in the Python API
341+
-------------------------
342+
343+
* The *etype* parameters of the :func:`~traceback.format_exception`,
344+
:func:`~traceback.format_exception_only`, and
345+
:func:`~traceback.print_exception` functions in the :mod:`traceback` module
346+
have been renamed to *exc*.
347+
(Contributed by Zackery Spytz and Matthias Bussonnier in :issue:`26389`.)
348+
331349

332350
Build Changes
333351
=============

Lib/test/test_traceback.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,26 @@ def test_print_exception(self):
212212
)
213213
self.assertEqual(output.getvalue(), "Exception: projector\n")
214214

215+
def test_print_exception_exc(self):
216+
output = StringIO()
217+
traceback.print_exception(Exception("projector"), file=output)
218+
self.assertEqual(output.getvalue(), "Exception: projector\n")
219+
220+
def test_format_exception_exc(self):
221+
e = Exception("projector")
222+
output = traceback.format_exception(e)
223+
self.assertEqual(output, ["Exception: projector\n"])
224+
with self.assertRaisesRegex(ValueError, 'Both or neither'):
225+
traceback.format_exception(e.__class__, e)
226+
with self.assertRaisesRegex(ValueError, 'Both or neither'):
227+
traceback.format_exception(e.__class__, tb=e.__traceback__)
228+
with self.assertRaisesRegex(TypeError, 'positional-only'):
229+
traceback.format_exception(exc=e)
230+
231+
def test_format_exception_only_exc(self):
232+
output = traceback.format_exception_only(Exception("projector"))
233+
self.assertEqual(output, ["Exception: projector\n"])
234+
215235

216236
class TracebackFormatTests(unittest.TestCase):
217237

Lib/traceback.py

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,19 @@ def extract_tb(tb, limit=None):
8484
"another exception occurred:\n\n")
8585

8686

87-
def print_exception(etype, value, tb, limit=None, file=None, chain=True):
87+
_sentinel = object()
88+
89+
90+
def _parse_value_tb(exc, value, tb):
91+
if (value is _sentinel) != (tb is _sentinel):
92+
raise ValueError("Both or neither of value and tb must be given")
93+
if value is tb is _sentinel:
94+
return exc, exc.__traceback__
95+
return value, tb
96+
97+
98+
def print_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \
99+
file=None, chain=True):
88100
"""Print exception up to 'limit' stack trace entries from 'tb' to 'file'.
89101
90102
This differs from print_tb() in the following ways: (1) if
@@ -95,17 +107,16 @@ def print_exception(etype, value, tb, limit=None, file=None, chain=True):
95107
occurred with a caret on the next line indicating the approximate
96108
position of the error.
97109
"""
98-
# format_exception has ignored etype for some time, and code such as cgitb
99-
# passes in bogus values as a result. For compatibility with such code we
100-
# ignore it here (rather than in the new TracebackException API).
110+
value, tb = _parse_value_tb(exc, value, tb)
101111
if file is None:
102112
file = sys.stderr
103113
for line in TracebackException(
104114
type(value), value, tb, limit=limit).format(chain=chain):
105115
print(line, file=file, end="")
106116

107117

108-
def format_exception(etype, value, tb, limit=None, chain=True):
118+
def format_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \
119+
chain=True):
109120
"""Format a stack trace and the exception information.
110121
111122
The arguments have the same meaning as the corresponding arguments
@@ -114,19 +125,15 @@ def format_exception(etype, value, tb, limit=None, chain=True):
114125
these lines are concatenated and printed, exactly the same text is
115126
printed as does print_exception().
116127
"""
117-
# format_exception has ignored etype for some time, and code such as cgitb
118-
# passes in bogus values as a result. For compatibility with such code we
119-
# ignore it here (rather than in the new TracebackException API).
128+
value, tb = _parse_value_tb(exc, value, tb)
120129
return list(TracebackException(
121130
type(value), value, tb, limit=limit).format(chain=chain))
122131

123132

124-
def format_exception_only(etype, value):
133+
def format_exception_only(exc, /, value=_sentinel):
125134
"""Format the exception part of a traceback.
126135
127-
The arguments are the exception type and value such as given by
128-
sys.last_type and sys.last_value. The return value is a list of
129-
strings, each ending in a newline.
136+
The return value is a list of strings, each ending in a newline.
130137
131138
Normally, the list contains a single string; however, for
132139
SyntaxError exceptions, it contains several lines that (when
@@ -137,7 +144,10 @@ def format_exception_only(etype, value):
137144
string in the list.
138145
139146
"""
140-
return list(TracebackException(etype, value, None).format_exception_only())
147+
if value is _sentinel:
148+
value = exc
149+
return list(TracebackException(
150+
type(value), value, None).format_exception_only())
141151

142152

143153
# -- not official API but folk probably use these two functions.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
The :func:`traceback.format_exception`,
2+
:func:`traceback.format_exception_only`, and
3+
:func:`traceback.print_exception` functions can now take an exception object
4+
as a positional-only argument.

0 commit comments

Comments
 (0)