[Python-checkins] gh-90501: Add PyErr_GetHandledException and PyErr_SetHandledException (GH-30531)

iritkatriel webhook-mailer at python.org
Fri Apr 15 14:57:54 EDT 2022


https://github.com/python/cpython/commit/5d421d7342fc0d278c129c05bea7028430e94a4e
commit: 5d421d7342fc0d278c129c05bea7028430e94a4e
branch: main
author: Irit Katriel <1055913+iritkatriel at users.noreply.github.com>
committer: iritkatriel <1055913+iritkatriel at users.noreply.github.com>
date: 2022-04-15T19:57:47+01:00
summary:

gh-90501: Add PyErr_GetHandledException and PyErr_SetHandledException (GH-30531)

files:
A Misc/NEWS.d/next/C API/2022-01-11-12-52-37.bpo-46343.JQJWhZ.rst
M Doc/c-api/exceptions.rst
M Doc/data/stable_abi.dat
M Doc/library/sys.rst
M Doc/whatsnew/3.11.rst
M Include/cpython/pyerrors.h
M Include/pyerrors.h
M Lib/test/test_capi.py
M Lib/test/test_stable_abi_ctypes.py
M Misc/stable_abi.txt
M Modules/_testcapimodule.c
M PC/python3dll.c
M Python/errors.c

diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst
index a5a93d0ebbf28..7bfeca5958cc4 100644
--- a/Doc/c-api/exceptions.rst
+++ b/Doc/c-api/exceptions.rst
@@ -460,12 +460,46 @@ Querying the error indicator
          }
 
 
-.. c:function:: void PyErr_GetExcInfo(PyObject **ptype, PyObject **pvalue, PyObject **ptraceback)
+.. c:function:: PyObject* PyErr_GetHandledException(void)
+
+   Retrieve the active exception instance, as would be returned by :func:`sys.exception`.
+   This refers to an exception that was *already caught*, not to an exception that was
+   freshly raised. Returns a new reference to the exception or ``NULL``.
+   Does not modify the interpreter's exception state.
+
+   .. note::
+
+      This function is not normally used by code that wants to handle exceptions.
+      Rather, it can be used when code needs to save and restore the exception
+      state temporarily.  Use :c:func:`PyErr_SetHandledException` to restore or
+      clear the exception state.
+
+   .. versionadded:: 3.11
 
-   Retrieve the exception info, as known from ``sys.exc_info()``.  This refers
+.. c:function:: void PyErr_SetHandledException(PyObject *exc)
+
+   Set the active exception, as known from ``sys.exception()``.  This refers
    to an exception that was *already caught*, not to an exception that was
-   freshly raised.  Returns new references for the three objects, any of which
-   may be ``NULL``.  Does not modify the exception info state.
+   freshly raised.
+   To clear the exception state, pass ``NULL``.
+
+   .. note::
+
+      This function is not normally used by code that wants to handle exceptions.
+      Rather, it can be used when code needs to save and restore the exception
+      state temporarily.  Use :c:func:`PyErr_GetHandledException` to get the exception
+      state.
+
+   .. versionadded:: 3.11
+
+.. c:function:: void PyErr_GetExcInfo(PyObject **ptype, PyObject **pvalue, PyObject **ptraceback)
+
+   Retrieve the old-style representation of the exception info, as known from
+   :func:`sys.exc_info`.  This refers to an exception that was *already caught*,
+   not to an exception that was freshly raised.  Returns new references for the
+   three objects, any of which may be ``NULL``.  Does not modify the exception
+   info state.  This function is kept for backwards compatibility. Prefer using
+   :c:func:`PyErr_GetHandledException`.
 
    .. note::
 
@@ -483,6 +517,8 @@ Querying the error indicator
    to an exception that was *already caught*, not to an exception that was
    freshly raised.  This function steals the references of the arguments.
    To clear the exception state, pass ``NULL`` for all three arguments.
+   This function is kept for backwards compatibility. Prefer using
+   :c:func:`PyErr_SetHandledException`.
 
    .. note::
 
diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat
index 849a2cfd51f24..5387d0bf983fa 100644
--- a/Doc/data/stable_abi.dat
+++ b/Doc/data/stable_abi.dat
@@ -137,6 +137,7 @@ function,PyErr_Fetch,3.2,,
 function,PyErr_Format,3.2,,
 function,PyErr_FormatV,3.5,,
 function,PyErr_GetExcInfo,3.7,,
+function,PyErr_GetHandledException,3.11,,
 function,PyErr_GivenExceptionMatches,3.2,,
 function,PyErr_NewException,3.2,,
 function,PyErr_NewExceptionWithDoc,3.2,,
@@ -159,6 +160,7 @@ function,PyErr_SetFromErrnoWithFilenameObject,3.2,,
 function,PyErr_SetFromErrnoWithFilenameObjects,3.7,,
 function,PyErr_SetFromWindowsErr,3.7,on Windows,
 function,PyErr_SetFromWindowsErrWithFilename,3.7,on Windows,
+function,PyErr_SetHandledException,3.11,,
 function,PyErr_SetImportError,3.7,,
 function,PyErr_SetImportErrorSubclass,3.6,,
 function,PyErr_SetInterrupt,3.2,,
diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst
index 126da31b5bd32..2a8b532b592e8 100644
--- a/Doc/library/sys.rst
+++ b/Doc/library/sys.rst
@@ -381,19 +381,12 @@ always available.
 
 .. 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.
+   This function, when called while an exception handler is executing (such as
+   an ``except`` or ``except*`` clause), returns the exception instance that
+   was caught by this handler. When exception handlers are nested within one
+   another, only the exception handled by the innermost handler is accessible.
 
-   .. index:: object: traceback
-
-   If no exception is being handled anywhere on the stack, ``None`` is
-   returned.
+   If no exception handler is executing, this function returns ``None``.
 
    .. versionadded:: 3.11
 
diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst
index a5a52682b503c..b6f47f532ca4c 100644
--- a/Doc/whatsnew/3.11.rst
+++ b/Doc/whatsnew/3.11.rst
@@ -1161,6 +1161,14 @@ New Features
   :c:func:`PyFrame_GetBuiltins`, :c:func:`PyFrame_GetGenerator`,
   :c:func:`PyFrame_GetGlobals`, :c:func:`PyFrame_GetLasti`.
 
+* Added two new functions to get and set the active exception instance:
+  :c:func:`PyErr_GetHandledException` and :c:func:`PyErr_SetHandledException`.
+  These are alternatives to :c:func:`PyErr_SetExcInfo()` and
+  :c:func:`PyErr_GetExcInfo()` which work with the legacy 3-tuple
+  representation of exceptions.
+  (Contributed by Irit Katriel in :issue:`46343`.)
+
+
 Porting to Python 3.11
 ----------------------
 
diff --git a/Include/cpython/pyerrors.h b/Include/cpython/pyerrors.h
index 5281fde1f1a54..08630cce8ac90 100644
--- a/Include/cpython/pyerrors.h
+++ b/Include/cpython/pyerrors.h
@@ -91,6 +91,8 @@ typedef PyOSErrorObject PyWindowsErrorObject;
 
 PyAPI_FUNC(void) _PyErr_SetKeyError(PyObject *);
 PyAPI_FUNC(_PyErr_StackItem*) _PyErr_GetTopmostException(PyThreadState *tstate);
+PyAPI_FUNC(PyObject*) _PyErr_GetHandledException(PyThreadState *);
+PyAPI_FUNC(void) _PyErr_SetHandledException(PyThreadState *, PyObject *);
 PyAPI_FUNC(void) _PyErr_GetExcInfo(PyThreadState *, PyObject **, PyObject **, PyObject **);
 
 /* Context manipulation (PEP 3134) */
diff --git a/Include/pyerrors.h b/Include/pyerrors.h
index 77d791427d492..34e3de3328f41 100644
--- a/Include/pyerrors.h
+++ b/Include/pyerrors.h
@@ -18,6 +18,10 @@ PyAPI_FUNC(PyObject *) PyErr_Occurred(void);
 PyAPI_FUNC(void) PyErr_Clear(void);
 PyAPI_FUNC(void) PyErr_Fetch(PyObject **, PyObject **, PyObject **);
 PyAPI_FUNC(void) PyErr_Restore(PyObject *, PyObject *, PyObject *);
+#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030b0000
+PyAPI_FUNC(PyObject*) PyErr_GetHandledException(void);
+PyAPI_FUNC(void) PyErr_SetHandledException(PyObject *);
+#endif
 #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x03030000
 PyAPI_FUNC(void) PyErr_GetExcInfo(PyObject **, PyObject **, PyObject **);
 PyAPI_FUNC(void) PyErr_SetExcInfo(PyObject *, PyObject *, PyObject *);
diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py
index 40e4774c6b8ed..eb0edbf5a3a12 100644
--- a/Lib/test/test_capi.py
+++ b/Lib/test/test_capi.py
@@ -88,6 +88,28 @@ def test_no_FatalError_infinite_loop(self):
     def test_memoryview_from_NULL_pointer(self):
         self.assertRaises(ValueError, _testcapi.make_memoryview_from_NULL_pointer)
 
+    def test_exception(self):
+        raised_exception = ValueError("5")
+        new_exc = TypeError("TEST")
+        try:
+            raise raised_exception
+        except ValueError as e:
+            orig_sys_exception = sys.exception()
+            orig_exception = _testcapi.set_exception(new_exc)
+            new_sys_exception = sys.exception()
+            new_exception = _testcapi.set_exception(orig_exception)
+            reset_sys_exception = sys.exception()
+
+            self.assertEqual(orig_exception, e)
+
+            self.assertEqual(orig_exception, raised_exception)
+            self.assertEqual(orig_sys_exception, orig_exception)
+            self.assertEqual(reset_sys_exception, orig_exception)
+            self.assertEqual(new_exception, new_exc)
+            self.assertEqual(new_sys_exception, new_exception)
+        else:
+            self.fail("Exception not raised")
+
     def test_exc_info(self):
         raised_exception = ValueError("5")
         new_exc = TypeError("TEST")
diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py
index efd3b1b7cd2d2..0656ff5581be5 100644
--- a/Lib/test/test_stable_abi_ctypes.py
+++ b/Lib/test/test_stable_abi_ctypes.py
@@ -152,6 +152,7 @@ def test_available_symbols(self):
     "PyErr_Format",
     "PyErr_FormatV",
     "PyErr_GetExcInfo",
+    "PyErr_GetHandledException",
     "PyErr_GivenExceptionMatches",
     "PyErr_NewException",
     "PyErr_NewExceptionWithDoc",
@@ -168,6 +169,7 @@ def test_available_symbols(self):
     "PyErr_SetFromErrnoWithFilename",
     "PyErr_SetFromErrnoWithFilenameObject",
     "PyErr_SetFromErrnoWithFilenameObjects",
+    "PyErr_SetHandledException",
     "PyErr_SetImportError",
     "PyErr_SetImportErrorSubclass",
     "PyErr_SetInterrupt",
diff --git a/Misc/NEWS.d/next/C API/2022-01-11-12-52-37.bpo-46343.JQJWhZ.rst b/Misc/NEWS.d/next/C API/2022-01-11-12-52-37.bpo-46343.JQJWhZ.rst
new file mode 100644
index 0000000000000..1ac8da853c879
--- /dev/null
+++ b/Misc/NEWS.d/next/C API/2022-01-11-12-52-37.bpo-46343.JQJWhZ.rst	
@@ -0,0 +1,5 @@
+Added :c:func:`PyErr_GetHandledException` and
+:c:func:`PyErr_SetHandledException` as simpler alternatives to
+:c:func:`PyErr_GetExcInfo` and :c:func:`PyErr_SetExcInfo`.
+
+They are included in the stable ABI.
diff --git a/Misc/stable_abi.txt b/Misc/stable_abi.txt
index 4864bf319a76f..66777a62c4301 100644
--- a/Misc/stable_abi.txt
+++ b/Misc/stable_abi.txt
@@ -2253,3 +2253,8 @@ function PyMemoryView_FromBuffer
 
 data Py_Version
     added 3.11
+function PyErr_GetHandledException
+    added 3.11
+function PyErr_SetHandledException
+    added 3.11
+
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index 13dd29427aa2c..71683abebb231 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -2562,6 +2562,16 @@ set_errno(PyObject *self, PyObject *args)
     Py_RETURN_NONE;
 }
 
+static PyObject *
+test_set_exception(PyObject *self, PyObject *new_exc)
+{
+    PyObject *exc = PyErr_GetHandledException();
+    assert(PyExceptionInstance_Check(exc) || exc == NULL);
+
+    PyErr_SetHandledException(new_exc);
+    return exc;
+}
+
 static PyObject *
 test_set_exc_info(PyObject *self, PyObject *args)
 {
@@ -6068,6 +6078,7 @@ static PyMethodDef TestMethods[] = {
 #endif
     {"traceback_print",         traceback_print,                 METH_VARARGS},
     {"exception_print",         exception_print,                 METH_VARARGS},
+    {"set_exception",           test_set_exception,              METH_O},
     {"set_exc_info",            test_set_exc_info,               METH_VARARGS},
     {"argparsing",              argparsing,                      METH_VARARGS},
     {"code_newempty",           code_newempty,                   METH_VARARGS},
diff --git a/PC/python3dll.c b/PC/python3dll.c
index 70f11dc190554..0aee2aec84726 100755
--- a/PC/python3dll.c
+++ b/PC/python3dll.c
@@ -196,6 +196,7 @@ EXPORT_FUNC(PyErr_Fetch)
 EXPORT_FUNC(PyErr_Format)
 EXPORT_FUNC(PyErr_FormatV)
 EXPORT_FUNC(PyErr_GetExcInfo)
+EXPORT_FUNC(PyErr_GetHandledException)
 EXPORT_FUNC(PyErr_GivenExceptionMatches)
 EXPORT_FUNC(PyErr_NewException)
 EXPORT_FUNC(PyErr_NewExceptionWithDoc)
@@ -218,6 +219,7 @@ EXPORT_FUNC(PyErr_SetFromErrnoWithFilenameObject)
 EXPORT_FUNC(PyErr_SetFromErrnoWithFilenameObjects)
 EXPORT_FUNC(PyErr_SetFromWindowsErr)
 EXPORT_FUNC(PyErr_SetFromWindowsErrWithFilename)
+EXPORT_FUNC(PyErr_SetHandledException)
 EXPORT_FUNC(PyErr_SetImportError)
 EXPORT_FUNC(PyErr_SetImportErrorSubclass)
 EXPORT_FUNC(PyErr_SetInterrupt)
diff --git a/Python/errors.c b/Python/errors.c
index e170c9dff2dbb..ce7785855b8e5 100644
--- a/Python/errors.c
+++ b/Python/errors.c
@@ -499,6 +499,38 @@ _PyErr_GetExcInfo(PyThreadState *tstate,
     Py_XINCREF(*p_traceback);
 }
 
+PyObject*
+_PyErr_GetHandledException(PyThreadState *tstate)
+{
+    _PyErr_StackItem *exc_info = _PyErr_GetTopmostException(tstate);
+    PyObject *exc = exc_info->exc_value;
+    if (exc == NULL || exc == Py_None) {
+        return NULL;
+    }
+    return Py_NewRef(exc);
+}
+
+PyObject*
+PyErr_GetHandledException(void)
+{
+    PyThreadState *tstate = _PyThreadState_GET();
+    return _PyErr_GetHandledException(tstate);
+}
+
+void
+_PyErr_SetHandledException(PyThreadState *tstate, PyObject *exc)
+{
+    PyObject *oldexc = tstate->exc_info->exc_value;
+    tstate->exc_info->exc_value = Py_XNewRef(exc);
+    Py_XDECREF(oldexc);
+}
+
+void
+PyErr_SetHandledException(PyObject *exc)
+{
+    PyThreadState *tstate = _PyThreadState_GET();
+    _PyErr_SetHandledException(tstate, exc);
+}
 
 void
 PyErr_GetExcInfo(PyObject **p_type, PyObject **p_value, PyObject **p_traceback)
@@ -510,17 +542,10 @@ PyErr_GetExcInfo(PyObject **p_type, PyObject **p_value, PyObject **p_traceback)
 void
 PyErr_SetExcInfo(PyObject *type, PyObject *value, PyObject *traceback)
 {
-    PyThreadState *tstate = _PyThreadState_GET();
-
-    PyObject *oldvalue = tstate->exc_info->exc_value;
-
-    tstate->exc_info->exc_value = value;
-
+    PyErr_SetHandledException(value);
     /* These args are no longer used, but we still need to steal a ref */
     Py_XDECREF(type);
     Py_XDECREF(traceback);
-
-    Py_XDECREF(oldvalue);
 }
 
 



More information about the Python-checkins mailing list