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

pablogsal webhook-mailer at python.org
Thu Nov 5 17:18:59 EST 2020


https://github.com/python/cpython/commit/91e93794d5dd1aa91fbe142099c2955e0c4c1660
commit: 91e93794d5dd1aa91fbe142099c2955e0c4c1660
branch: master
author: Zackery Spytz <zspytz at gmail.com>
committer: pablogsal <Pablogsal at gmail.com>
date: 2020-11-05T22:18:44Z
summary:

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 at gmail.com>

files:
A Misc/NEWS.d/next/Library/2020-10-08-23-51-55.bpo-26389.uga44e.rst
M Doc/library/traceback.rst
M Doc/whatsnew/3.10.rst
M Lib/test/test_traceback.py
M Lib/traceback.py

diff --git a/Doc/library/traceback.rst b/Doc/library/traceback.rst
index 462a6a5566e20..c233f18d30a29 100644
--- a/Doc/library/traceback.rst
+++ b/Doc/library/traceback.rst
@@ -36,7 +36,8 @@ The module defines the following functions:
        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
    *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
      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
 
@@ -53,6 +54,10 @@ The module defines the following functions:
      format, it prints the line where the syntax error occurred with a caret
      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`.
    If *chain* is true (the default), then chained exceptions (the
    :attr:`__cause__` or :attr:`__context__` attributes of the exception) will be
@@ -62,6 +67,10 @@ The module defines the following functions:
    .. versionchanged:: 3.5
       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)
 
@@ -121,18 +130,26 @@ The module defines the following functions:
    text line is not ``None``.
 
 
-.. function:: format_exception_only(etype, value)
+.. function:: format_exception_only(exc, /[, value])
+
+   Format the exception part of a traceback using an exception value such as
+   given by ``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 :exc:`SyntaxError` exceptions, it contains several lines that (when
+   printed) display detailed information about where the syntax error occurred.
+   The message indicating which exception occurred is the always last string in
+   the list.
 
-   Format the exception part of a traceback.  The arguments are the exception
-   type and value such as given by ``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 :exc:`SyntaxError`
-   exceptions, it contains several lines that (when printed) display detailed
-   information about where the syntax error occurred.  The message indicating
-   which exception occurred is the always last string in 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
    same meaning as the corresponding arguments to :func:`print_exception`.  The
@@ -143,6 +160,10 @@ The module defines the following functions:
    .. versionchanged:: 3.5
       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)
 
diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst
index bac1a2e678309..0ed7084ccd2ff 100644
--- a/Doc/whatsnew/3.10.rst
+++ b/Doc/whatsnew/3.10.rst
@@ -232,6 +232,15 @@ retrieve the functions set by :func:`threading.settrace` and
 :func:`threading.setprofile` respectively.
 (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
 -----
 
@@ -328,6 +337,15 @@ This section lists previously described changes and other bugfixes
 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
 =============
diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py
index 730596efd8bce..91688ff72bbea 100644
--- a/Lib/test/test_traceback.py
+++ b/Lib/test/test_traceback.py
@@ -212,6 +212,26 @@ def test_print_exception(self):
         )
         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):
 
diff --git a/Lib/traceback.py b/Lib/traceback.py
index a19e38718b120..d2d93c8a32ac2 100644
--- a/Lib/traceback.py
+++ b/Lib/traceback.py
@@ -84,7 +84,19 @@ def extract_tb(tb, limit=None):
     "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'.
 
     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
     position of the error.
     """
-    # format_exception has ignored etype for some time, and code such as cgitb
-    # passes in bogus values as a result. For compatibility with such code we
-    # ignore it here (rather than in the new TracebackException API).
+    value, tb = _parse_value_tb(exc, value, tb)
     if file is None:
         file = sys.stderr
     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="")
 
 
-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.
 
     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
     printed as does print_exception().
     """
-    # format_exception has ignored etype for some time, and code such as cgitb
-    # passes in bogus values as a result. For compatibility with such code we
-    # ignore it here (rather than in the new TracebackException API).
+    value, tb = _parse_value_tb(exc, value, tb)
     return list(TracebackException(
         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.
 
-    The arguments are the exception type and value such as given by
-    sys.last_type and sys.last_value. The return value is a list of
-    strings, each ending in a newline.
+    The return value is a list of strings, each ending in a newline.
 
     Normally, the list contains a single string; however, for
     SyntaxError exceptions, it contains several lines that (when
@@ -137,7 +144,10 @@ def format_exception_only(etype, value):
     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.
diff --git a/Misc/NEWS.d/next/Library/2020-10-08-23-51-55.bpo-26389.uga44e.rst b/Misc/NEWS.d/next/Library/2020-10-08-23-51-55.bpo-26389.uga44e.rst
new file mode 100644
index 0000000000000..a721a0d7cd0e8
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2020-10-08-23-51-55.bpo-26389.uga44e.rst
@@ -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.



More information about the Python-checkins mailing list