Repository navigation
Simplify the interpreter's (type, val, tb) exception representation #89874
Description
Activity
Exceptions are represented in the interpreter as (type, val, tb) triplets which most of the time contain redundant information (the type is the type of val and the tb is also on the exception). This complicates the code and is inefficient as opcodes that manage exceptions push and pop 3 items for each exception.
We will change the internal representation to be (1) just the exception value if it is normalised and (2) a tuple of the 3 values for the uncommon case where they are all needed.
See also faster-cpython/ideas#106.
- added3.11only security fixesonly security fixesinterpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)
on Nov 4, 2021 Would there be any change at the Python level?
- addedperformancePerformance or resource usagePerformance or resource usage
on Nov 6, 2021 Initially not, neither in python nor in the c api.
It would be nice to replace PyErr_Fetch/Restore by a version that takes just an exception but that’s a long deprecation.
Following the analysis/discussion on faster-cpython/ideas#106:
-
exc_info is always normalized, so we actually will never need to create a tuple of these three values (exc_curinfo, the exception raised but not yet caught can be unnormalized, but it is not pushed/popped on the stack).
-
We will reduce the interpreter's exc_info representation to just the exception instance.
-
There are two APIs that are impacted, both in non-documented edge cases:
-
sys.exc_info()[2] can currently be different from sys.exc_info()[1].__traceback__ because changes to the latter (while an except clause is executing) don't show up in the former. This is arguably a bug, we will change it so that the type and traceback are always consistent with the exception.
-
PyErr_SetExcInfo does no arg checking, and will set exc_info to an inconsistent triplet if you ask it to. However, the exc_value arg must be an exception instance so the only thing you can do is pass in nonsensical args where the type/traceback do not match the exception. This function's purpose is to save/restore exc_info. We will make it ignore the type and traceback and document that change.
-
12 remaining items
That commit has significant changes in ceval.c and compile.c. They don't need to be reverted to unbreak cython. I'm working on a PR for a simpler change.
Have you tried with CYTHON_USE_EXC_INFO_STACK undefined?
If this is still the position of cython maintainers:
then I will need to revert the change until 3.12.
Time to insist on directly communicating with the Cython team (esp. @scoder) and broker some kind of compromise.
__Pyx_PyErr_GetTopmostException(PyThreadState *tstate)
Python provides a *private* _PyErr_GetTopmostException(tstate) function, but Cython reimplements its own function. I'm not sure why.
#74716 proposes adding PyErr_GetActiveException() function which has no parameter, but Cython __Pyx_PyErr_GetTopmostException() has a tstate parameter.
Simplified example numpy/random/_mt19937.c code:
static CYTHON_INLINE void __Pyx__ExceptionSave(PyThreadState *tstate, PyObject **type, PyObject **value, PyObject **tb) { _PyErr_StackItem *exc_info = __Pyx_PyErr_GetTopmostException(tstate); *type = exc_info->exc_type; *value = exc_info->exc_value; *tb = exc_info->exc_traceback; Py_XINCREF(*type); Py_XINCREF(*value); Py_XINCREF(*tb); } static CYTHON_INLINE void __Pyx__ExceptionReset(PyThreadState *tstate, PyObject *type, PyObject *value, PyObject *tb) { PyObject *tmp_type, *tmp_value, *tmp_tb; _PyErr_StackItem *exc_info = tstate->exc_info; tmp_type = exc_info->exc_type; tmp_value = exc_info->exc_value; tmp_tb = exc_info->exc_traceback; exc_info->exc_type = type; exc_info->exc_value = value; exc_info->exc_traceback = tb; Py_XDECREF(tmp_type); Py_XDECREF(tmp_value); Py_XDECREF(tmp_tb); }
Cython saves/restores the current exception of tstate. Maybe we need to provide a high-level API for that as well?
This is a backport of @scoder's patch to 0.29.x. (I don't know if this is helpful).
https://github.com/cython/cython/compare/master...iritkatriel:exc_info?expand=1
#74716 proposes adding PyErr_GetActiveException() function which has no parameter, but Cython __Pyx_PyErr_GetTopmostException() has a tstate parameter.
I've now updated it to follow the pattern of other functions, where the is a private function that takes tstate and the public function calls it.
So it adds in Include/pyerrors.h
PyAPI_FUNC(PyObject*) PyErr_GetActiveException(void); PyAPI_FUNC(void) PyErr_SetActiveException(PyObject *);
and in Include/cpython/pyerrors.h
PyAPI_FUNC(PyObject*) _PyErr_GetActiveException(PyThreadState *); PyAPI_FUNC(void) _PyErr_SetActiveException(PyThreadState *, PyObject *);
Just a quick comment on Cython and these changes:
Cython 0.29 can build itself on Python 3.11a4 with
CFLAGS="-DCYTHON_FAST_THREAD_STATE=0 -DCYTHON_USE_EXC_INFO_STACK=0" python3.11 setup.py build_ext.I think there's some coroutine code left that isn't fixed by those flags, but a large chunk of Cython libraries won't use coroutines and so will work fine with these flags. Hopefully that unblocks some stuff for you.
lxml does crash on the current Cython 0.29.x development branch.
We have agreed on python-dev [1] that the cpython changes will not be reverted, and the issue will be fixed in cython. So I am closing this again.
- added a commit that references this issue
on May 23, 2022 - added a commit that references this issue
on Jul 11, 2025
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields: