How Python Maintains Tracebacks for Unhandled Exceptions
When an unhandled exception occurs in Python, the interpreter prints a detailed traceback showing the exact call sequence that caused the failure. Python maintains this information by inspecting the runtime call stack as the exception propagates, dynamically linking execution frames into a specialized traceback object, attaching that object directly to the exception instance, and passing it to a default handler that prints the error to the standard error stream.
Frame Objects and Execution State
At runtime, Python represents each function call using an internal
structure called a frame object (PyFrameObject in CPython).
Every time a function is invoked, a new frame is pushed onto Python's
execution stack. A frame object stores critical execution context,
including:
- The code object containing bytecode instructions.
- The current instruction pointer (
f_lasti), which indicates the exact bytecode instruction being executed. - Local and global variable namespaces.
- A reference to the calling frame (
f_back), forming the call stack hierarchy.
Traceback Construction During Unwinding
When an exception is raised via the raise statement or
by an internal runtime error, Python begins stack unwinding. As the
runtime searches up the call stack for an enclosing
try...except block, it constructs a traceback object
(PyTracebackObject).
Traceback objects are organized as a singly linked list. Each node in the list captures a snapshot of a specific frame at the moment the exception passed through it. Specifically, a traceback node records:
tb_frame: A reference to the execution frame.tb_lineno: The source code line number corresponding to the current instruction pointer.tb_lasti: The index of the last bytecode instruction executed within that frame.tb_next: A pointer to the next traceback node higher up the call chain.
As the exception propagates upward through callers that do not handle it, Python prepends or appends new traceback nodes to maintain the sequential history of the call chain from the root frame down to the origin of the error.
Attachment to the Exception Instance
Modern Python versions (Python 3+) store the traceback directly on
the exception instance itself via the __traceback__
attribute. When an exception is raised, Python automatically updates
this attribute with the newly assembled traceback chain.
If an exception is caught and re-raised, or if another exception is
raised while handling the first, Python preserves the original traceback
by setting the __context__ or __cause__
attributes on the new exception, allowing chains of tracebacks to
persist across multiple distinct error events.
Unhandled Exceptions and
sys.excepthook
If the exception reaches the top of the call stack without being
caught by any except block, it becomes an unhandled
exception. Python handles this final state through
sys.excepthook:
- The interpreter extracts the exception type, the exception value
(the instance), and the associated traceback object from
instance.__traceback__. - These three components are passed to
sys.excepthook(type, value, traceback). - The default implementation of
sys.excepthooktraverses the linked list of traceback nodes from oldest to newest. For each node, it reads the associated code object, resolves the file name and line number, retrieves the corresponding source code line from disk or memory caches, and formats the output. - The formatted string is written to
sys.stderr, and the interpreter process exits with a non-zero status code.