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:
- C side: code calls
PyErr_SetString(PyExc_ValueError, "bad")(orPyErr_Format,PyErr_SetFromErrno, …). This sets the per-thread exception fields (tstate->current_exception). - Python side: the bytecode reads
current_exceptionafter every operation that can fail. Theerror:label inPython/ceval.cis reached when an opcode returnsNULLor-1, and it transfers control toexception_unwindwhich 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:
BaseExceptionGroupandExceptionGrouptypes —Objects/exceptions.c.except*opcodes —CHECK_EG_MATCH,PUSH_EXC_INFO,RERAISE— defined inPython/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.splitmethod.
Exception classes
The hierarchy is in Objects/exceptions.c. The full list is in Doc/library/exceptions.rst. Notable bits:
BaseExceptionitself storesargsand__traceback__.OSErrorconsolidates the historicalIOError,EnvironmentError, and several POSIX-specific exceptions. Subclasses likeFileNotFoundErrorandPermissionErrorare dispatched on byerrnoinPyErr_SetFromErrnoWithFilename.KeyboardInterruptis set by the SIGINT handler installed inModules/signalmodule.cand delivered between bytecodes by_PyEval_AddPendingCallinPython/ceval.c.SystemExitis a "nice" exception — the defaultsys.excepthookdoesn't print a traceback for it, just exits with the code.
Warnings
warnings and _warnings use the same machinery:
- The C core is in
Python/_warnings.c. - The Python wrapper is
Lib/warnings.pyand (alternative pure-Python implementation)Lib/_py_warnings.py.
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
- New built-in exception class →
Objects/exceptions.c+Doc/library/exceptions.rst. - New raise opcode / unwind path →
Python/bytecodes.c+Python/codegen.c. - Better error message in a stdlib module → use
PyErr_Formator the higher-level helpers and add__notes__(PEP 678) for context. - New "did you mean…" suggestion →
Python/suggestions.c. - Exception-table format →
Python/assemble.cand the design doc inInternalDocs/exception_handling.md.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.