Skip to main content

Python/compile.c (part 13)

Source:

cpython 3.14 @ ab2d84fe1023/Python/compile.c

This annotation covers comprehension compilation. See compile12_detail for compiler_function, compiler_class, and closure handling.

Map

LinesSymbolRole
1-80compiler_comprehensionEntry point for all comprehension types
81-180Inlined comprehensionCompile directly into enclosing scope (3.12+)
181-280Non-inlined comprehensionEmit as nested function + CALL
281-380compiler_listcomp[x for x in it if cond]
381-600compiler_genexp / compiler_setcomp / compiler_dictcompOther forms

Reading

compiler_comprehension

// CPython: Python/compile.c:5420 compiler_comprehension
static int
compiler_comprehension(struct compiler *c, expr_ty e, int type,
identifier name, asdl_comprehension_seq *generators,
expr_ty elt, expr_ty val)
{
PySTEntryObject *entry = _PyST_Lookup(c->c_st, (PyObject *)e);
int is_inlined = entry->ste_comp_inlined;
if (is_inlined) {
return compiler_comprehension_inlined(c, e, type, generators, elt, val);
}
return compiler_comprehension_nested(c, e, type, name, generators, elt, val);
}

Since Python 3.12, CPython inlines comprehensions that don't require a new scope (no walrus operator leaking into enclosing scope, no yield inside). The symbol table determines this at analysis time.

Inlined comprehension

// CPython: Python/compile.c:5460 compiler_comprehension_inlined
static int
compiler_comprehension_inlined(struct compiler *c, ...)
{
/* No new frame: compile directly into the current function's bytecode.
The iteration variable is local to the comprehension but in the
enclosing frame's localsplus. */
VISIT(c, expr, outermost_iter); /* push the iterable */
compiler_comprehension_generator(c, generators, 0, 0, elt, val, type);
/* Result: list/set/dict/generator left on stack */
}

Inlined comprehensions don't create a nested function call. The iteration is compiled directly into the outer function's bytecode, with the loop variable stored in dedicated localsplus slots. This eliminates the overhead of creating and calling a nested function.

Non-inlined comprehension

// CPython: Python/compile.c:5510 compiler_comprehension_nested
static int
compiler_comprehension_nested(struct compiler *c, ...)
{
/* Compile as: def <listcomp>(iter): ... */
compiler_enter_scope(c, name, COMPILER_SCOPE_COMPREHENSION, ...);
/* Inside new scope: .0 is the outermost iterator */
compiler_comprehension_generator(c, generators, 0, 0, elt, val, type);
ADDOP(c, RETURN_VALUE);
compiler_exit_scope(c);
/* In outer scope: push iterator, MAKE_FUNCTION, CALL */
VISIT(c, expr, outermost_iter);
ADDOP_I(c, GET_ITER, 0);
...
ADDOP_I(c, CALL, 1);
}

Non-inlined comprehensions are compiled as nested functions. The outermost iterable is evaluated in the enclosing scope (before MAKE_FUNCTION) and passed as the first argument. Inner for clauses are evaluated inside the function.

compiler_listcomp

// CPython: Python/compile.c:5600 compiler_listcomp
static int
compiler_listcomp(struct compiler *c, expr_ty e)
{
assert(e->kind == ListComp_kind);
return compiler_comprehension(c, e, COMP_LISTCOMP, &_Py_ID(listcomp),
e->v.ListComp.generators,
e->v.ListComp.elt, NULL);
}

List, set, dict comprehensions, and generator expressions all route through compiler_comprehension with a different type constant (COMP_LISTCOMP, COMP_SETCOMP, COMP_DICTCOMP, COMP_GENEXP). The type controls which BUILD_LIST/SET_ADD/MAP_ADD/YIELD_VALUE opcodes are emitted.

Generator expressions

// CPython: Python/compile.c:5640 compiler_genexp
static int
compiler_genexp(struct compiler *c, expr_ty e)
{
/* Generator expressions are always non-inlined:
they need a frame to hold the suspended state */
return compiler_comprehension(c, e, COMP_GENEXP, &_Py_ID(genexpr),
e->v.GeneratorExp.generators,
e->v.GeneratorExp.elt, NULL);
}

Generator expressions ((x for x in it)) are never inlined even in Python 3.12+: they need a suspended frame (generator object) to implement lazy evaluation. RETURN_GENERATOR is emitted at the start of the compiled function.

gopy notes

compiler_comprehension is in compile/codegen_expr.go. Inlined comprehensions are compiled with emitComprehensionInlined. Non-inlined call emitComprehensionNested which calls compileFunction for the inner scope. The type byte maps to LIST_APPEND/SET_ADD/MAP_ADD in compile/codegen_stmt.go.