Skip to content

Commit 5d421d7

Browse files
authored
gh-90501: Add PyErr_GetHandledException and PyErr_SetHandledException (GH-30531)
1 parent c06a4ff commit 5d421d7

File tree

13 files changed

+141
-24
lines changed

13 files changed

+141
-24
lines changed

Doc/c-api/exceptions.rst

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -460,12 +460,46 @@ Querying the error indicator
460460
}
461461
462462
463-
.. c:function:: void PyErr_GetExcInfo(PyObject **ptype, PyObject **pvalue, PyObject **ptraceback)
463+
.. c:function:: PyObject* PyErr_GetHandledException(void)
464+
465+
Retrieve the active exception instance, as would be returned by :func:`sys.exception`.
466+
This refers to an exception that was *already caught*, not to an exception that was
467+
freshly raised. Returns a new reference to the exception or ``NULL``.
468+
Does not modify the interpreter's exception state.
469+
470+
.. note::
471+
472+
This function is not normally used by code that wants to handle exceptions.
473+
Rather, it can be used when code needs to save and restore the exception
474+
state temporarily. Use :c:func:`PyErr_SetHandledException` to restore or
475+
clear the exception state.
476+
477+
.. versionadded:: 3.11
464478
465-
Retrieve the exception info, as known from ``sys.exc_info()``. This refers
479+
.. c:function:: void PyErr_SetHandledException(PyObject *exc)
480+
481+
Set the active exception, as known from ``sys.exception()``. This refers
466482
to an exception that was *already caught*, not to an exception that was
467-
freshly raised. Returns new references for the three objects, any of which
468-
may be ``NULL``. Does not modify the exception info state.
483+
freshly raised.
484+
To clear the exception state, pass ``NULL``.
485+
486+
.. note::
487+
488+
This function is not normally used by code that wants to handle exceptions.
489+
Rather, it can be used when code needs to save and restore the exception
490+
state temporarily. Use :c:func:`PyErr_GetHandledException` to get the exception
491+
state.
492+
493+
.. versionadded:: 3.11
494+
495+
.. c:function:: void PyErr_GetExcInfo(PyObject **ptype, PyObject **pvalue, PyObject **ptraceback)
496+
497+
Retrieve the old-style representation of the exception info, as known from
498+
:func:`sys.exc_info`. This refers to an exception that was *already caught*,
499+
not to an exception that was freshly raised. Returns new references for the
500+
three objects, any of which may be ``NULL``. Does not modify the exception
501+
info state. This function is kept for backwards compatibility. Prefer using
502+
:c:func:`PyErr_GetHandledException`.
469503
470504
.. note::
471505
@@ -483,6 +517,8 @@ Querying the error indicator
483517
to an exception that was *already caught*, not to an exception that was
484518
freshly raised. This function steals the references of the arguments.
485519
To clear the exception state, pass ``NULL`` for all three arguments.
520+
This function is kept for backwards compatibility. Prefer using
521+
:c:func:`PyErr_SetHandledException`.
486522
487523
.. note::
488524

Doc/data/stable_abi.dat

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Doc/library/sys.rst

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -381,19 +381,12 @@ always available.
381381

382382
.. function:: exception()
383383

384-
This function returns the exception instance that is currently being
385-
handled. This exception is specific both to the current thread and
386-
to the current stack frame. If the current stack frame is not handling
387-
an exception, the exception is taken from the calling stack frame, or its
388-
caller, and so on until a stack frame is found that is handling an
389-
exception. Here, "handling an exception" is defined as "executing an
390-
except clause." For any stack frame, only the exception being currently
391-
handled is accessible.
384+
This function, when called while an exception handler is executing (such as
385+
an ``except`` or ``except*`` clause), returns the exception instance that
386+
was caught by this handler. When exception handlers are nested within one
387+
another, only the exception handled by the innermost handler is accessible.
392388

393-
.. index:: object: traceback
394-
395-
If no exception is being handled anywhere on the stack, ``None`` is
396-
returned.
389+
If no exception handler is executing, this function returns ``None``.
397390

398391
.. versionadded:: 3.11
399392

Doc/whatsnew/3.11.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1161,6 +1161,14 @@ New Features
11611161
:c:func:`PyFrame_GetBuiltins`, :c:func:`PyFrame_GetGenerator`,
11621162
:c:func:`PyFrame_GetGlobals`, :c:func:`PyFrame_GetLasti`.
11631163

1164+
* Added two new functions to get and set the active exception instance:
1165+
:c:func:`PyErr_GetHandledException` and :c:func:`PyErr_SetHandledException`.
1166+
These are alternatives to :c:func:`PyErr_SetExcInfo()` and
1167+
:c:func:`PyErr_GetExcInfo()` which work with the legacy 3-tuple
1168+
representation of exceptions.
1169+
(Contributed by Irit Katriel in :issue:`46343`.)
1170+
1171+
11641172
Porting to Python 3.11
11651173
----------------------
11661174

Include/cpython/pyerrors.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ typedef PyOSErrorObject PyWindowsErrorObject;
9191

9292
PyAPI_FUNC(void) _PyErr_SetKeyError(PyObject *);
9393
PyAPI_FUNC(_PyErr_StackItem*) _PyErr_GetTopmostException(PyThreadState *tstate);
94+
PyAPI_FUNC(PyObject*) _PyErr_GetHandledException(PyThreadState *);
95+
PyAPI_FUNC(void) _PyErr_SetHandledException(PyThreadState *, PyObject *);
9496
PyAPI_FUNC(void) _PyErr_GetExcInfo(PyThreadState *, PyObject **, PyObject **, PyObject **);
9597

9698
/* Context manipulation (PEP 3134) */

Include/pyerrors.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ PyAPI_FUNC(PyObject *) PyErr_Occurred(void);
1818
PyAPI_FUNC(void) PyErr_Clear(void);
1919
PyAPI_FUNC(void) PyErr_Fetch(PyObject **, PyObject **, PyObject **);
2020
PyAPI_FUNC(void) PyErr_Restore(PyObject *, PyObject *, PyObject *);
21+
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030b0000
22+
PyAPI_FUNC(PyObject*) PyErr_GetHandledException(void);
23+
PyAPI_FUNC(void) PyErr_SetHandledException(PyObject *);
24+
#endif
2125
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x03030000
2226
PyAPI_FUNC(void) PyErr_GetExcInfo(PyObject **, PyObject **, PyObject **);
2327
PyAPI_FUNC(void) PyErr_SetExcInfo(PyObject *, PyObject *, PyObject *);

Lib/test/test_capi.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,28 @@ def test_no_FatalError_infinite_loop(self):
8888
def test_memoryview_from_NULL_pointer(self):
8989
self.assertRaises(ValueError, _testcapi.make_memoryview_from_NULL_pointer)
9090

91+
def test_exception(self):
92+
raised_exception = ValueError("5")
93+
new_exc = TypeError("TEST")
94+
try:
95+
raise raised_exception
96+
except ValueError as e:
97+
orig_sys_exception = sys.exception()
98+
orig_exception = _testcapi.set_exception(new_exc)
99+
new_sys_exception = sys.exception()
100+
new_exception = _testcapi.set_exception(orig_exception)
101+
reset_sys_exception = sys.exception()
102+
103+
self.assertEqual(orig_exception, e)
104+
105+
self.assertEqual(orig_exception, raised_exception)
106+
self.assertEqual(orig_sys_exception, orig_exception)
107+
self.assertEqual(reset_sys_exception, orig_exception)
108+
self.assertEqual(new_exception, new_exc)
109+
self.assertEqual(new_sys_exception, new_exception)
110+
else:
111+
self.fail("Exception not raised")
112+
91113
def test_exc_info(self):
92114
raised_exception = ValueError("5")
93115
new_exc = TypeError("TEST")

Lib/test/test_stable_abi_ctypes.py

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Added :c:func:`PyErr_GetHandledException` and
2+
:c:func:`PyErr_SetHandledException` as simpler alternatives to
3+
:c:func:`PyErr_GetExcInfo` and :c:func:`PyErr_SetExcInfo`.
4+
5+
They are included in the stable ABI.

Misc/stable_abi.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2253,3 +2253,8 @@ function PyMemoryView_FromBuffer
22532253

22542254
data Py_Version
22552255
added 3.11
2256+
function PyErr_GetHandledException
2257+
added 3.11
2258+
function PyErr_SetHandledException
2259+
added 3.11
2260+

0 commit comments

Comments
 (0)