[Python-checkins] bpo-46328: Add sys.exception() (GH-30514)

iritkatriel webhook-mailer at python.org
Thu Jan 13 07:36:02 EST 2022


https://github.com/python/cpython/commit/c590b581bba517f81ced2e6f531ccc9e2e22eab5
commit: c590b581bba517f81ced2e6f531ccc9e2e22eab5
branch: main
author: Irit Katriel <1055913+iritkatriel at users.noreply.github.com>
committer: iritkatriel <1055913+iritkatriel at users.noreply.github.com>
date: 2022-01-13T12:35:58Z
summary:

bpo-46328: Add sys.exception() (GH-30514)

files:
A Misc/NEWS.d/next/Library/2022-01-10-11-53-15.bpo-46328.6i9Wvq.rst
M Doc/library/sys.rst
M Doc/tutorial/errors.rst
M Doc/whatsnew/3.11.rst
M Lib/test/test_sys.py
M Python/clinic/sysmodule.c.h
M Python/sysmodule.c

diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst
index 7d1b21f05edb1..5e47201f88eae 100644
--- a/Doc/library/sys.rst
+++ b/Doc/library/sys.rst
@@ -378,26 +378,41 @@ always available.
    .. versionadded:: 3.8
       __unraisablehook__
 
+
+.. function:: exception()
+
+   This function returns the exception instance that is currently being
+   handled.  This exception is specific both to the current thread and
+   to the current stack frame.  If the current stack frame is not handling
+   an exception, the exception is taken from the calling stack frame, or its
+   caller, and so on until a stack frame is found that is handling an
+   exception.  Here, "handling an exception" is defined as "executing an
+   except clause." For any stack frame, only the exception being currently
+   handled is accessible.
+
+   .. index:: object: traceback
+
+   If no exception is being handled anywhere on the stack, ``None`` is
+   returned.
+
+   .. versionadded:: 3.11
+
+
 .. function:: exc_info()
 
-   This function returns a tuple of three values that give information about the
-   exception that is currently being handled.  The information returned is specific
-   both to the current thread and to the current stack frame.  If the current stack
-   frame is not handling an exception, the information is taken from the calling
-   stack frame, or its caller, and so on until a stack frame is found that is
-   handling an exception.  Here, "handling an exception" is defined as "executing
-   an except clause."  For any stack frame, only information about the exception
-   being currently handled is accessible.
+   This function returns the old-style representation of the handled
+   exception. If an exception ``e`` is currently handled (so
+   :func:`exception` would return ``e``), :func:`exc_info` returns the
+   tuple ``(type(e), e, e.__traceback__)``.
+   That is, a tuple containing the type of the exception (a subclass of
+   :exc:`BaseException`), the exception itself, and a :ref:`traceback
+   object <traceback-objects>` which typically encapsulates the call
+   stack at the point where the exception last occurred.
 
    .. index:: object: traceback
 
-   If no exception is being handled anywhere on the stack, a tuple containing
-   three ``None`` values is returned.  Otherwise, the values returned are
-   ``(type, value, traceback)``.  Their meaning is: *type* gets the type of the
-   exception being handled (a subclass of :exc:`BaseException`); *value* gets
-   the exception instance (an instance of the exception type); *traceback* gets
-   a :ref:`traceback object <traceback-objects>` which typically encapsulates
-   the call stack at the point where the exception last occurred.
+   If no exception is being handled anywhere on the stack, this function
+   return a tuple containing three ``None`` values.
 
    .. versionchanged:: 3.11
       The ``type`` and ``traceback`` fields are now derived from the ``value``
diff --git a/Doc/tutorial/errors.rst b/Doc/tutorial/errors.rst
index ad1ef841bffc4..888740cdd0f19 100644
--- a/Doc/tutorial/errors.rst
+++ b/Doc/tutorial/errors.rst
@@ -167,7 +167,7 @@ then re-raise the exception (allowing a caller to handle the exception as well):
        raise
 
 Alternatively the last except clause may omit the exception name(s), however the exception
-value must then be retrieved from ``sys.exc_info()[1]``.
+value must then be retrieved with ``sys.exception()``.
 
 The :keyword:`try` ... :keyword:`except` statement has an optional *else
 clause*, which, when present, must follow all *except clauses*.  It is useful
diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst
index 72243619891ae..28ac57e954438 100644
--- a/Doc/whatsnew/3.11.rst
+++ b/Doc/whatsnew/3.11.rst
@@ -305,6 +305,9 @@ sys
   the results of subsequent calls to :func:`exc_info`.
   (Contributed by Irit Katriel in :issue:`45711`.)
 
+* Add :func:`sys.exception` which returns the active exception instance
+  (equivalent to ``sys.exc_info()[1]``).
+  (Contributed by Irit Katriel in :issue:`46328`.)
 
 threading
 ---------
diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py
index 38771d427da7b..f05cd75af97b5 100644
--- a/Lib/test/test_sys.py
+++ b/Lib/test/test_sys.py
@@ -71,6 +71,69 @@ def baddisplayhook(obj):
             code = compile("42", "<string>", "single")
             self.assertRaises(ValueError, eval, code)
 
+class ActiveExceptionTests(unittest.TestCase):
+    def test_exc_info_no_exception(self):
+        self.assertEqual(sys.exc_info(), (None, None, None))
+
+    def test_sys_exception_no_exception(self):
+        self.assertEqual(sys.exception(), None)
+
+    def test_exc_info_with_exception_instance(self):
+        def f():
+            raise ValueError(42)
+
+        try:
+            f()
+        except Exception as e_:
+            e = e_
+            exc_info = sys.exc_info()
+
+        self.assertIsInstance(e, ValueError)
+        self.assertIs(exc_info[0], ValueError)
+        self.assertIs(exc_info[1], e)
+        self.assertIs(exc_info[2], e.__traceback__)
+
+    def test_exc_info_with_exception_type(self):
+        def f():
+            raise ValueError
+
+        try:
+            f()
+        except Exception as e_:
+            e = e_
+            exc_info = sys.exc_info()
+
+        self.assertIsInstance(e, ValueError)
+        self.assertIs(exc_info[0], ValueError)
+        self.assertIs(exc_info[1], e)
+        self.assertIs(exc_info[2], e.__traceback__)
+
+    def test_sys_exception_with_exception_instance(self):
+        def f():
+            raise ValueError(42)
+
+        try:
+            f()
+        except Exception as e_:
+            e = e_
+            exc = sys.exception()
+
+        self.assertIsInstance(e, ValueError)
+        self.assertIs(exc, e)
+
+    def test_sys_exception_with_exception_type(self):
+        def f():
+            raise ValueError
+
+        try:
+            f()
+        except Exception as e_:
+            e = e_
+            exc = sys.exception()
+
+        self.assertIsInstance(e, ValueError)
+        self.assertIs(exc, e)
+
 
 class ExceptHookTest(unittest.TestCase):
 
diff --git a/Misc/NEWS.d/next/Library/2022-01-10-11-53-15.bpo-46328.6i9Wvq.rst b/Misc/NEWS.d/next/Library/2022-01-10-11-53-15.bpo-46328.6i9Wvq.rst
new file mode 100644
index 0000000000000..fec790d52cef3
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-01-10-11-53-15.bpo-46328.6i9Wvq.rst
@@ -0,0 +1 @@
+Added the :meth:`sys.exception` method which returns the active exception instance.
\ No newline at end of file
diff --git a/Python/clinic/sysmodule.c.h b/Python/clinic/sysmodule.c.h
index 8350fbf98561a..ce5390c8a1e58 100644
--- a/Python/clinic/sysmodule.c.h
+++ b/Python/clinic/sysmodule.c.h
@@ -76,6 +76,28 @@ sys_excepthook(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
     return return_value;
 }
 
+PyDoc_STRVAR(sys_exception__doc__,
+"exception($module, /)\n"
+"--\n"
+"\n"
+"Return the current exception.\n"
+"\n"
+"Return the most recent exception caught by an except clause\n"
+"in the current stack frame or in an older stack frame, or None\n"
+"if no such exception exists.");
+
+#define SYS_EXCEPTION_METHODDEF    \
+    {"exception", (PyCFunction)sys_exception, METH_NOARGS, sys_exception__doc__},
+
+static PyObject *
+sys_exception_impl(PyObject *module);
+
+static PyObject *
+sys_exception(PyObject *module, PyObject *Py_UNUSED(ignored))
+{
+    return sys_exception_impl(module);
+}
+
 PyDoc_STRVAR(sys_exc_info__doc__,
 "exc_info($module, /)\n"
 "--\n"
@@ -992,4 +1014,4 @@ sys_getandroidapilevel(PyObject *module, PyObject *Py_UNUSED(ignored))
 #ifndef SYS_GETANDROIDAPILEVEL_METHODDEF
     #define SYS_GETANDROIDAPILEVEL_METHODDEF
 #endif /* !defined(SYS_GETANDROIDAPILEVEL_METHODDEF) */
-/*[clinic end generated code: output=855fc93b2347710b input=a9049054013a1b77]*/
+/*[clinic end generated code: output=60756bc6f683e0c8 input=a9049054013a1b77]*/
diff --git a/Python/sysmodule.c b/Python/sysmodule.c
index f912115560704..0b7b61d8b1e28 100644
--- a/Python/sysmodule.c
+++ b/Python/sysmodule.c
@@ -771,6 +771,28 @@ sys_excepthook_impl(PyObject *module, PyObject *exctype, PyObject *value,
 }
 
 
+/*[clinic input]
+sys.exception
+
+Return the current exception.
+
+Return the most recent exception caught by an except clause
+in the current stack frame or in an older stack frame, or None
+if no such exception exists.
+[clinic start generated code]*/
+
+static PyObject *
+sys_exception_impl(PyObject *module)
+/*[clinic end generated code: output=2381ee2f25953e40 input=c88fbb94b6287431]*/
+{
+    _PyErr_StackItem *err_info = _PyErr_GetTopmostException(_PyThreadState_GET());
+    if (err_info->exc_value != NULL) {
+        return Py_NewRef(err_info->exc_value);
+    }
+    Py_RETURN_NONE;
+}
+
+
 /*[clinic input]
 sys.exc_info
 
@@ -1963,6 +1985,7 @@ static PyMethodDef sys_methods[] = {
     SYS__CURRENT_FRAMES_METHODDEF
     SYS__CURRENT_EXCEPTIONS_METHODDEF
     SYS_DISPLAYHOOK_METHODDEF
+    SYS_EXCEPTION_METHODDEF
     SYS_EXC_INFO_METHODDEF
     SYS_EXCEPTHOOK_METHODDEF
     SYS_EXIT_METHODDEF
@@ -2457,7 +2480,8 @@ Functions:\n\
 \n\
 displayhook() -- print an object to the screen, and save it in builtins._\n\
 excepthook() -- print an exception and its traceback to sys.stderr\n\
-exc_info() -- return thread-safe information about the current exception\n\
+exception() -- return the current thread's active exception\n\
+exc_info() -- return information about the current thread's active exception\n\
 exit() -- exit the interpreter by raising SystemExit\n\
 getdlopenflags() -- returns flags to be used for dlopen() calls\n\
 getprofile() -- get the global profiling function\n\



More information about the Python-checkins mailing list