Open-Source Wikis

/

CPython

/

Systems

/

Exception handling

python/cpython

Exception handling

Python exceptions look like Python objects to the user, but the implementation has two halves: the exception types (a hierarchy of classes implemented in C) and the dispatch mechanism in the interpreter (the per-code-object exception table). Since 3.11 there are no SETUP_FINALLY / POP_BLOCK opcodes — exception delivery is data-driven from a table compiled into the code object. The authoritative reference is InternalDocs/exception_handling.md.

Files

File Role
Python/errors.c C-level error-setting API: PyErr_SetString, PyErr_Format, PyErr_NormalizeException, ….
Objects/exceptions.c The class hierarchy (BaseException and 70+ subclasses), pickling support, traceback objects.
Python/traceback.c Traceback object construction, formatted output, traceback module support.
Python/suggestions.c The "did you mean…" hints attached to NameError / AttributeError / ImportError.
Python/codegen.c Emits the exception table for a code object.
Python/assemble.c Encodes the exception table into the code object.
Include/cpython/pyerrors.h Public API.

The exception table (since 3.11)

Each PyCodeObject carries a co_exceptiontable byte string. It maps bytecode offsets to handler offsets, plus the depth of the value stack at handler entry and a flag indicating whether the handler is a lasti (re-raise context). The format is a varint-encoded sequence of (start, length, target, depth, lasti) tuples — see InternalDocs/exception_handling.md for the bit layout.

This is zero-cost in the no-exception path: there are no SETUP_*/POP_BLOCK opcodes that the normal flow has to execute. The lookup only happens when an exception is raised.

graph LR
    OP[Opcode body] -->|raise| ERR[goto error label]
    ERR -->|exception_unwind| LOOKUP[Look up handler in co_exceptiontable]
    LOOKUP -->|found| HANDLER[Restore stack to depth, jump to handler]
    LOOKUP -->|not found| UNWIND[Pop frame, propagate to caller]

Raising an exception

Two layers:

  1. C side: code calls PyErr_SetString(PyExc_ValueError, "bad") (or PyErr_Format, PyErr_SetFromErrno, …). This sets the per-thread exception fields (tstate->current_exception).
  2. Python side: the bytecode reads current_exception after every operation that can fail. The error: label in Python/ceval.c is reached when an opcode returns NULL or -1, and it transfers control to exception_unwind which consults the exception table.

A raise statement in Python is compiled to one of RAISE_VARARGS, RERAISE, or CHECK_EXC_MATCH + RAISE_VARARGS. The opcodes are defined in Python/bytecodes.c.

Tracebacks

A PyTracebackObject (Python/traceback.c) is a singly-linked list whose head is the deepest frame at the time of the raise. Each call to PyTraceBack_Here(frame) (called from exception_unwind) prepends a new entry. The exception object's __traceback__ attribute holds the head.

When CPython prints an exception (the default sys.excepthook), it walks the traceback chain plus __cause__ / __context__ (PEP 3134) and uses the per-line column information added in 3.11 (PEP 657) to point at the offending sub-expression with carets. The column data lives in co_linetable; the printer is in Python/traceback.c.

"Did you mean…?"

Since 3.10, certain errors get suggestions:

  • NameError: name 'foo' is not defined. Did you mean 'food'?
  • AttributeError: ... Did you mean 'startswith'?
  • ImportError: cannot import name 'X' from 'Y'. Did you mean: 'Z'?

Implementation: Python/suggestions.c. It uses Damerau-Levenshtein distance against the available names; the candidate set is gathered from the frame's locals, the module's globals, or the type's MRO. The trigger code lives in BaseException.__init__ and the relevant raise sites.

try/except* and exception groups

PEP 654 added BaseExceptionGroup, ExceptionGroup, and the except* syntax. Implementation:

  • BaseExceptionGroup and ExceptionGroup types — Objects/exceptions.c.
  • except* opcodes — CHECK_EG_MATCH, PUSH_EXC_INFO, RERAISE — defined in Python/bytecodes.c.
  • The split/preserve semantics (matching exceptions are routed to the handler, the rest re-raised together) — handled in the codegen and the BaseExceptionGroup.split method.

Exception classes

The hierarchy is in Objects/exceptions.c. The full list is in Doc/library/exceptions.rst. Notable bits:

  • BaseException itself stores args and __traceback__.
  • OSError consolidates the historical IOError, EnvironmentError, and several POSIX-specific exceptions. Subclasses like FileNotFoundError and PermissionError are dispatched on by errno in PyErr_SetFromErrnoWithFilename.
  • KeyboardInterrupt is set by the SIGINT handler installed in Modules/signalmodule.c and delivered between bytecodes by _PyEval_AddPendingCall in Python/ceval.c.
  • SystemExit is a "nice" exception — the default sys.excepthook doesn't print a traceback for it, just exits with the code.

Warnings

warnings and _warnings use the same machinery:

A warning is technically a raised exception that the warnings filter chooses to handle by printing rather than propagating. This means simplefilter('error') reuses the exception machinery directly.

Common patterns when writing C

PyObject *res = PyDict_GetItemWithError(d, key);  // returns borrowed ref or NULL
if (res == NULL) {
    if (PyErr_Occurred()) {
        return NULL;     // propagate
    }
    PyErr_SetString(PyExc_KeyError, "...");
    return NULL;
}

The split between "no exception, key missing" and "exception, lookup failed" is one of the recurring bugs in C extension code. Use the *WithError variants when in doubt.

For "set an OS error":

if (rc < 0) {
    return PyErr_SetFromErrnoWithFilename(PyExc_OSError, path);
}

For "chain to an existing exception":

PyObject *prev_type, *prev_val, *prev_tb;
PyErr_Fetch(&prev_type, &prev_val, &prev_tb);
PyErr_NormalizeException(&prev_type, &prev_val, &prev_tb);
PyErr_SetString(PyExc_RuntimeError, "while doing X");
_PyErr_ChainExceptions1(prev_val);   // sets __context__

Entry points for modification

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Exception handling – CPython wiki | Factory