How Does the Python VM Capture Exceptions?
The article explains Python's exception handling implementation, detailing how the virtual machine executes try/except/else/finally blocks, the bytecode instructions generated for each clause, the static exception table used for fast dispatch, and the effects of return and del statements on control flow and object lifetimes.
After reviewing Python's exception raising mechanism, the article examines how the interpreter captures exceptions. A complete try statement consists of try, optional except clauses, an optional else, and an optional finally. The execution paths are illustrated with concrete examples showing which blocks run when no exception occurs, when an exception is caught, and when it propagates.
Exception‑capture bytecode
Compiling a simple snippet that raises an Exception, catches it, prints it, and runs a finally block yields the following disassembly (Python 3.12):
0 RESUME 0
2 NOP
4 PUSH_NULL
6 LOAD_NAME 0 (Exception)
8 LOAD_CONST 0 ('抛出一个异常')
10 CALL 1
18 RAISE_VARARGS 1
20 PUSH_EXC_INFO
22 LOAD_NAME 0 (Exception)
24 CHECK_EXC_MATCH
26 POP_JUMP_IF_FALSE 64
28 STORE_NAME 1 (e)
30 PUSH_NULL
32 LOAD_NAME 2 (print)
34 LOAD_NAME 1 (e)
36 CALL 1
44 POP_TOP
46 POP_EXCEPT
48 LOAD_CONST 1 (None)
50 STORE_NAME 1 (e)
52 DELETE_NAME 1 (e)
54 JUMP_FORWARD 8
56 ... (exception cleanup) ...
66 ... (more cleanup) ...
72 NOP
74 PUSH_NULL
76 LOAD_NAME 2 (print)
78 LOAD_CONST 2 ('我一定会被执行的')
80 CALL 1
88 POP_TOP
90 RETURN_CONST 1 (None)The RAISE_VARARGS instruction creates the exception object and stores it in the thread state via PyErr_Restore, then jumps to the exception_unwind label. The unwind code looks up the static co_exceptiontable to find the appropriate handler offset, pushes the exception onto the runtime stack, and transfers control to the matching except or finally block.
Key bytecode operations
TRY clause : loads the exception class, loads the message constant, calls the class to create an exception object, and executes RAISE_VARARGS.
EXCEPT clause : PUSH_EXC_INFO saves the previous exception, CHECK_EXC_MATCH compares the raised exception with the caught type, and POP_JUMP_IF_FALSE skips the block if the types do not match. If matched, the exception object is stored in the target variable ( e).
POP_EXCEPT : restores the previous exception in the thread state after the handler finishes.
DELETE_NAME : implements the implicit del e that Python performs at the end of an except block to break reference cycles.
RETURN handling : if a return appears inside a finally, its value overrides any earlier return or exception, and the exception is discarded.
Static exception table
The table, stored in co_exceptiontable, maps bytecode ranges to handler offsets, e.g.:
4‑18 → 20 (try block jumps to 20 on error)
20‑28 → 66 (except block cleanup)
30‑44 → 56 (except body cleanup)
46‑54 → 92 (del e when finally present)
56‑64 → 66 (del e without finally)
66‑70 → 92 (exception cleanup)
92‑110 → 112 (finally block)This static representation, introduced in Python 3.11 and refined in 3.12, replaces the older dynamic stack of SETUP_FINALLY and makes dispatch faster, comparable to Java's exception tables.
Return‑value interactions
Several examples demonstrate how return statements interact with try, except, and finally:
def retval():
try:
return 123
except Exception:
return 456
finally:
return
print(retval()) # NoneWhen finally contains a return, that value is always returned, and any pending exception is suppressed.
Traceback construction
If an exception propagates out of all handlers, the interpreter walks back through stack frames, building a traceback object that records file names, line numbers, and function names. The traceback is then printed, showing the call chain from the module level down to the point where the error occurred.
In summary, Python’s exception capture relies on a static exception table and a series of well‑defined bytecode instructions. The mechanism incurs virtually no overhead when no exception is raised, and the explicit del of the exception variable prevents reference‑cycle memory leaks.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Satori Komeiji's Programming Classroom
Python and Rust developer; I write about any topics you're interested in. Follow me! (#^.^#)
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
