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:

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:

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:

  1. The interpreter extracts the exception type, the exception value (the instance), and the associated traceback object from instance.__traceback__.
  2. These three components are passed to sys.excepthook(type, value, traceback).
  3. The default implementation of sys.excepthook traverses 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.
  4. The formatted string is written to sys.stderr, and the interpreter process exits with a non-zero status code.