Skip to main content

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

LinesSymbolRolegopy
1-30_PyErr_StackItemOne 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_SetTopmostExceptionInline accessors that read/write tstate->exc_info->exc_value. Used by PUSH_EXC_INFO / POP_EXCEPT bytecodes.errors/api.go (Occurred, Raise)
61-100_PyBaseExceptionObjectInternal layout of every BaseException instance: args tuple, notes list, traceback, context, cause, suppress_context bit.errors/exception.go (Exception)
101-130_PyException_AddNoteAppends 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_GetValueInternal constructors and accessors for ExceptionGroup (PEP 654) and StopIteration.value.(pending)
161-180_PyErr_WriteUnraisableMsgWithExc / _PyErr_FormatNotePrint-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:

  1. Allocates a _PyErr_StackItem on the C stack of _PyEval_EvalFrameDefault.
  2. Moves tstate->current_exception into new_item->exc_value.
  3. Links new_item->previous_item = tstate->exc_info.
  4. 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 as context on 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 by raise X from Y. It overrides context in the printed traceback with the message "The above exception was the direct cause of the following exception".

  • __suppress_context__ (suppress_context) is set to 1 by raise X from Y, telling the traceback printer to use __cause__ and hide __context__. It is also set to 1 by raise X from None, in which case cause is NULL and both chaining displays are suppressed.

  • __notes__ (notes) holds a list of strings appended via add_note() (PEP 678, 3.11+). The interpreter does not use this field; it is purely for the traceback module and str() 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 fieldgopy Go field
_PyBaseExceptionObject.argserrors.Exception.Args *objects.Tuple
_PyBaseExceptionObject.noteserrors.Exception.Notes *objects.List
_PyBaseExceptionObject.tracebackerrors.Exception.TB *traceback.Traceback
_PyBaseExceptionObject.contexterrors.Exception.Context *Exception
_PyBaseExceptionObject.causeerrors.Exception.Cause *Exception
_PyBaseExceptionObject.suppress_contexterrors.Exception.Suppress bool
_PyErr_StackItem.exc_valuestate.Thread exception slot
tstate->exc_info linked listgoroutine stack (implicit)