Include/internal/pycore_exceptions.h
cpython 3.14 @ ab2d84fe1023/Include/internal/pycore_exceptions.h
The private companion to Include/pyerrors.h. The public header declares the
functions that raise and clear exceptions; this file exposes the structs that
hold exception state inside PyThreadState and the internal fields of
BaseException instances that are hidden from extension modules.
The central type is _PyErr_StackItem. Each Python frame's PUSH_EXC_INFO
bytecode allocates a stack item on the C stack and links it into a singly-linked
list rooted at tstate->exc_info. When the frame exits (via POP_EXCEPT), the
item is unlinked. This design gives each except: block its own exception slot
without heap allocation, and the linked list makes it straightforward for
sys.exc_info() to walk back to the innermost active exception.
The _PyBaseExceptionObject struct exposes the internal fields of every Python
exception instance. The public BaseException class maps these to args,
__traceback__, __cause__, __context__, and __suppress_context__
attributes. The C fields are accessed directly by the eval loop and the
except* handler for exception groups (PEP 654).
In gopy, errors.Exception in errors/exception.go is a direct port of
_PyBaseExceptionObject. The _PyErr_StackItem linked list is replaced by
gopy's frame-local exception slot on state.Thread, which serves the same
role without needing a C-stack-allocated struct.
Map
| Lines | Symbol | Role | gopy |
|---|---|---|---|
| 1-30 | _PyErr_StackItem | One node of the per-frame exception stack: exc_value (PyObject *) and previous_item pointer. PyThreadState.exc_info points at the topmost node. | state/thread.go (exception slot) |
| 31-60 | _PyErr_GetTopmostException / _PyErr_SetTopmostException | Inline accessors that read/write tstate->exc_info->exc_value. Used by PUSH_EXC_INFO / POP_EXCEPT bytecodes. | errors/api.go (Occurred, Raise) |
| 61-100 | _PyBaseExceptionObject | Internal layout of every BaseException instance: args tuple, notes list, traceback, context, cause, suppress_context bit. | errors/exception.go (Exception) |
| 101-130 | _PyException_AddNote | Appends a string to the exception's __notes__ list (PEP 678). Creates the list on first call. | errors/exception.go (Exception.Notes) |
| 131-160 | _PyBaseExceptionGroup_Create / _PyExcStopIteration_GetValue | Internal constructors and accessors for ExceptionGroup (PEP 654) and StopIteration.value. | (pending) |
| 161-180 | _PyErr_WriteUnraisableMsgWithExc / _PyErr_FormatNote | Print-path helpers called when an exception cannot be propagated (e.g. in a finalizer). | errors/print.go |
Reading
The exc_info stack (lines 1 to 60)
cpython 3.14 @ ab2d84fe1023/Include/internal/pycore_exceptions.h#L1-60
typedef struct _PyErr_StackItem {
/* The exception currently handled in this frame, or NULL.
In 3.11+ this is always a normalized BaseException instance. */
PyObject *exc_value;
/* Link to the enclosing frame's stack item, or
&tstate->exc_info_root for the outermost frame. */
struct _PyErr_StackItem *previous_item;
} _PyErr_StackItem;
PyThreadState carries exc_info, a pointer to the topmost _PyErr_StackItem.
The root item is PyThreadState.exc_info_root, which holds exc_value = NULL
and previous_item = NULL, acting as a sentinel.
When a frame executes PUSH_EXC_INFO, the bytecode:
- Allocates a
_PyErr_StackItemon the C stack of_PyEval_EvalFrameDefault. - Moves
tstate->current_exceptionintonew_item->exc_value. - Links
new_item->previous_item = tstate->exc_info. - Sets
tstate->exc_info = &new_item.
POP_EXCEPT reverses this: it pops tstate->exc_info back to
previous_item and decrements the outgoing exc_value.
sys.exc_info() walks the chain to find the first non-NULL exc_value,
which is the exception currently being handled in the innermost active
except clause.
static inline PyObject *
_PyErr_GetTopmostException(PyThreadState *tstate) {
_PyErr_StackItem *item = tstate->exc_info;
while ((item->exc_value == NULL || item->exc_value == Py_None)
&& item->previous_item != NULL) {
item = item->previous_item;
}
return item->exc_value;
}
The walk stops at the first non-empty slot. Py_None is a legacy marker used
by pre-3.11 code that set exc_value to Py_None instead of NULL for
"no exception". The loop skips these for backward compatibility.
In gopy, there is no C-stack-allocated struct. Each frame communicates with the
thread's exception slot directly via state.Thread.SetException and
state.Thread.CurrentException. The linked-list structure is not reproduced
because gopy frames are Go goroutine frames, not C stack frames, so the
per-frame allocations are handled by Go's stack growth mechanism.
_PyBaseExceptionObject layout and exception chaining (lines 61 to 130)
cpython 3.14 @ ab2d84fe1023/Include/internal/pycore_exceptions.h#L61-130
typedef struct {
PyObject_HEAD
PyObject *args; /* positional args tuple */
PyObject *notes; /* list[str] or NULL (PEP 678) */
PyObject *traceback; /* PyTracebackObject * or NULL */
PyObject *context; /* __context__: the active exc when this was raised */
PyObject *cause; /* __cause__: explicit `raise X from Y` target */
char suppress_context; /* 1 when `raise X from Y` was used */
} _PyBaseExceptionObject;
These five fields implement Python's exception chaining model (PEP 3134, 3.0):
-
__context__(context) is set automatically by the raise machinery. When an exception is raised while another exception is active, the eval loop stores the active exception ascontexton the new exception. This is the "implicit chaining" used for error messages like "During handling of the above exception, another exception occurred". -
__cause__(cause) is set explicitly byraise X from Y. It overridescontextin the printed traceback with the message "The above exception was the direct cause of the following exception". -
__suppress_context__(suppress_context) is set to1byraise X from Y, telling the traceback printer to use__cause__and hide__context__. It is also set to1byraise X from None, in which casecauseisNULLand both chaining displays are suppressed. -
__notes__(notes) holds a list of strings appended viaadd_note()(PEP 678, 3.11+). The interpreter does not use this field; it is purely for thetracebackmodule andstr()display.
/* Set by PUSH_EXC_INFO in the raise path */
if (prev_exc != NULL) {
exc->context = prev_exc; /* implicit chain */
}
/* Set by RAISE_VARARGS with a cause operand */
if (cause != NULL) {
exc->cause = cause;
exc->suppress_context = 1;
}
In gopy, errors.Exception mirrors all five fields directly:
Context *Exception, Cause *Exception, Suppress bool, Notes *objects.List,
and TB *traceback.Traceback. errors.Raise implements the implicit chaining
(if a previous exception is active and exc.Context == nil, it sets
exc.Context = prev). errors.RaiseFrom sets exc.Cause = cause and
exc.Suppress = true.
_PyException_AddNote (lines 101 to 130)
cpython 3.14 @ ab2d84fe1023/Include/internal/pycore_exceptions.h#L101-130
int _PyException_AddNote(PyObject *exc, PyObject *note);
add_note() is the Python-level method on BaseException that appends a
string to __notes__. The C implementation (_PyException_AddNote) creates
the notes list on first call with PyList_New(0), then calls
PyList_Append. The method raises TypeError if note is not a str.
The notes are printed after the exception message and traceback by
traceback.TracebackException. They are particularly useful in frameworks that
catch exceptions at a boundary and add contextual information before
re-raising:
try:
process(item)
except Exception as exc:
exc.add_note(f"Occurred while processing {item!r}")
raise
In gopy, errors.Exception carries Notes *objects.List. The add_note
method on gopy's exception type calls listAppend on that field, creating it
on first use in the same pattern as the C implementation.
gopy mirror
The full correspondence is:
| CPython C field | gopy Go field |
|---|---|
_PyBaseExceptionObject.args | errors.Exception.Args *objects.Tuple |
_PyBaseExceptionObject.notes | errors.Exception.Notes *objects.List |
_PyBaseExceptionObject.traceback | errors.Exception.TB *traceback.Traceback |
_PyBaseExceptionObject.context | errors.Exception.Context *Exception |
_PyBaseExceptionObject.cause | errors.Exception.Cause *Exception |
_PyBaseExceptionObject.suppress_context | errors.Exception.Suppress bool |
_PyErr_StackItem.exc_value | state.Thread exception slot |
tstate->exc_info linked list | goroutine stack (implicit) |