Skip to main content

Python/compile.c (part 11)

Source:

cpython 3.14 @ ab2d84fe1023/Python/compile.c

This annotation covers exception handling and context manager compilation. See python_compile10_detail for comprehension compilation.

Map

LinesSymbolRole
1-80compiler_withwith stmt as var:
81-160compiler_async_withasync with stmt as var:
161-280compiler_try_excepttry/except/else block
281-380compiler_try_finallytry/finally block
381-500Exception table generationMap bytecode ranges to handlers

Reading

compiler_with

// CPython: Python/compile.c:5680 compiler_with
static int
compiler_with(struct compiler *c, stmt_ty s, int pos)
{
/* with ctxmgr as var: body
=>
cm = ctxmgr
val = cm.__enter__()
try:
var = val
body
except:
if not cm.__exit__(*sys.exc_info()):
raise
else:
cm.__exit__(None, None, None)
*/
VISIT(c, expr, s->v.With.context_expr);
ADDOP(c, BEFORE_WITH); /* Call __enter__, push __exit__ */
/* Store val in var, or discard if 'with cm:' (no 'as') */
if (s->v.With.optional_vars) {
VISIT(c, expr, s->v.With.optional_vars);
} else {
ADDOP(c, POP_TOP);
}
/* Compile body in a try block */
...
ADDOP(c, WITH_EXCEPT_START); /* Call __exit__(*exc_info) */
...
}

BEFORE_WITH calls __enter__() and pushes the __exit__ method. On exception, WITH_EXCEPT_START calls __exit__(exc_type, exc_val, exc_tb). If __exit__ returns truthy, the exception is suppressed.

compiler_try_except

// CPython: Python/compile.c:5780 compiler_try_except
static int
compiler_try_except(struct compiler *c, stmt_ty s)
{
/* try: body
except ExcType as var: handler
else: else_body

Compiles to:
body
JUMP to else_body (no exception)
[exception table maps body range -> first except handler]
for each handler:
CHECK_EXC_MATCH ExcType
JUMP to next handler if no match
handler body
*/
...
}

try/except in Python 3.11+ uses an exception table instead of SETUP_FINALLY blocks. The bytecode is linear; the exception table is a separate structure mapping instruction ranges to handler addresses.

Exception table generation

// CPython: Python/compile.c:6100 compiler_add_exception_table_entry
static int
compiler_add_exception_table_entry(struct compiler *c,
Py_ssize_t start, Py_ssize_t end,
Py_ssize_t target, int depth,
int lasti)
{
/* Record: instructions [start, end) jump to target if an exception occurs.
depth: stack depth at the handler entry point.
lasti: whether to push the last instruction address on the handler entry. */
...
}

The exception table is emitted at the end of each code object. At runtime, when an exception occurs, _PyCode_InitAddressRange binary-searches the table to find the handler.

gopy notes

compiler_with is compile.compileWith in compile/codegen_stmt.go. It emits BEFORE_WITH and sets up an exception table entry covering the body. compiler_try_except is compile.compileTryExcept. Exception table entries are stored in compile.ExceptionTable and serialized to co_exceptiontable bytes.