Skip to main content

Exception Handling

Python uses “zero-cost” exception handling, which minimizes overhead when no exception occurs but efficiently handles exceptions when they are raised.

Zero-Cost Exception Handling

The key idea:
  • No exception: Virtually no overhead (no explicit checks)
  • Exception raised: Higher cost, but exceptions are rare
This contrasts with explicit error checking in languages like C, where every call must check for errors.

From Source to Bytecode

Consider this Python code:
It compiles to intermediate code with pseudo-instructions:

Pseudo-Instructions

SETUP_FINALLY and POP_BLOCK are pseudo-instructions:
  • Appear in intermediate code
  • Not actual bytecode instructions
  • SETUP_FINALLY: Specify exception handler location
  • POP_BLOCK: Restore previous exception handler

The Exception Table

These pseudo-instructions are converted to an exception table stored in co_exceptiontable:
  • Maps instruction offsets to exception handlers
  • Consulted only when an exception occurs
  • Instructions not covered by handlers don’t appear in the table
This achieves zero cost: no runtime checks when no exception occurs.

Handling Exceptions at Runtime

When an exception is raised:
  1. Interpreter calls get_exception_handler() in Python/ceval.c
  2. Looks up current instruction offset in exception table
  3. If handler found: Transfer control to handler
  4. If not found: Bubble up to caller’s frame
  5. Repeat until handler found or topmost frame reached

Traceback Construction

During unwinding, PyTraceBack_Here() (Python/traceback.c) adds each frame to the traceback.

Exception Table Entries

Each entry contains:
  • Handler location - Offset of exception handler
  • Stack depth - Stack depth at try statement
  • lasti flag - Whether to push instruction offset

Handler Execution Steps

  1. Pop values until stack depth matches handler’s depth
  2. If lasti is true, push raising instruction offset
  3. Push exception onto stack
  4. Jump to handler offset

Reraising Exceptions

The lasti (last instruction) flag supports exception reraising:
When reraising:
  1. lasti pushed to stack during exception handling
  2. RERAISE instruction (with oparg > 0) sets instruction pointer to lasti
  3. Traceback shows original raising location, not finally block

Exception Table Format

Conceptually, the table is a sequence of 5-tuples:
All offsets are in code units (not bytes).

Design Goals

  • Compact: Variable-sized entries for small offsets
  • Searchable: Binary search in O(log n) time

Encoding Strategy

Store as (start, size, target, depth, push_lasti) instead of (start, end, ...):
  • Size is always less than end offset
  • More compact encoding

Varint Encoding

Uses 7-bit variable-length encoding:
  • First byte: 1Xdddddd (1 = start bit, X = extend bit, d = data)
  • Continuation bytes: 0Xdddddd
  • Extend bit set if another byte follows

Depth and Lasti Combined

Encoded together as (depth << 1) | lasti before encoding.

Example Entry

Converts to:
Encodes as bytes:
Total: 5 bytes

Code References

Constructing the Table

assemble_exception_table() in Python/assemble.c

Looking Up Handlers

get_exception_handler() in Python/ceval.c

Parsing in Python

Defined in Lib/dis.py.

Exception Chaining

Exception chaining sets __context__ and __cause__ attributes.

Implicit Chaining (__context__)

Set automatically by _PyErr_SetObject() in Python/errors.c:
All PyErr_Set*() functions ultimately call _PyErr_SetObject().

Explicit Chaining (__cause__)

Set by RAISE_VARARGS bytecode:

Traceback Display

When displaying tracebacks:
  1. Show original exception first
  2. If __cause__ is set: Print “The above exception was the direct cause…”
  3. Else if __context__ is set and not suppressed: Print “During handling of the above exception…”
  4. Show new exception

Example: Exception Table in Action

Performance Implications

Fast Path (No Exception)

Performance:
  • No runtime overhead from try block
  • Same speed as code without exception handling
  • Exception table consulted only if exception raised

Exception Path

Performance:
  • Table lookup: O(log n) binary search
  • Stack unwinding: O(depth) to handler
  • Traceback construction: O(depth) frame additions
Exceptions are expensive, but the fast path is free.