Skip to main content

Python/compile.c (part 15)

Source:

cpython 3.14 @ ab2d84fe1023/Python/compile.c

This annotation covers comprehension compilation. See compile14_detail for compiler_try_except, compiler_with, and compiler_assert.

Map

LinesSymbolRole
1-80compiler_comprehensionShared logic for all comprehension forms
81-180compiler_listcomp[x for x in it if cond]
181-260compiler_setcomp{x for x in it}
261-340compiler_dictcomp{k: v for k, v in it}
341-500compiler_genexp(x for x in it) — returns a generator

Reading

compiler_comprehension

// CPython: Python/compile.c:4120 compiler_comprehension
static int
compiler_comprehension(struct compiler *c, expr_ty e, int type,
Py_ssize_t generators, PyObject *qualname)
{
/* Each comprehension compiles to a nested function:
def <listcomp>(it):
result = []
for x in it:
if cond:
result.append(x)
return result
The outer scope passes the first iterator as the argument.
*/
compiler_enter_scope(c, qualname, COMPILER_SCOPE_COMPREHENSION, ...);
/* First generator uses the .0 argument (the iterator) */
ADDOP_I(c, COPY_FREE_VARS, num_free);
/* Emit the loop body */
compiler_comprehension_generator(c, generators, 0, ...);
compiler_exit_scope(c);
/* In outer scope: make the iterator and call the nested function */
VISIT(c, expr, outermost.iter);
ADDOP(c, GET_ITER);
ADDOP_I(c, CALL, 1);
}

Comprehensions are desugared into immediately-called nested functions. This ensures that loop variables don't leak into the enclosing scope. The first iterator is passed as the .0 argument to avoid re-evaluating it inside the function.

compiler_listcomp

// CPython: Python/compile.c:4200 compiler_listcomp
static int
compiler_listcomp(struct compiler *c, expr_ty e)
{
/* Inside the comprehension function:
BUILD_LIST 0
<for loop body: LIST_APPEND>
RETURN_VALUE
*/
ADDOP_I(c, BUILD_LIST, 0);
compiler_comprehension(c, e, COMP_LISTCOMP, ...);
ADDOP(c, RETURN_VALUE);
}

The accumulator is a new list object built at the start of the function body. LIST_APPEND adds each element.

compiler_dictcomp

// CPython: Python/compile.c:4260 compiler_dictcomp
static int
compiler_dictcomp(struct compiler *c, expr_ty e)
{
/* Inside:
BUILD_MAP 0
<for loop body: MAP_ADD>
RETURN_VALUE
*/
ADDOP_I(c, BUILD_MAP, 0);
compiler_comprehension(c, e, COMP_DICTCOMP, ...);
ADDOP(c, RETURN_VALUE);
}

MAP_ADD i stores a (key, value) pair into the dict at STACK[-i]. For {k: v for k, v in d.items()}, k and v are pushed before MAP_ADD 1.

compiler_genexp

// CPython: Python/compile.c:4300 compiler_genexp
static int
compiler_genexp(struct compiler *c, expr_ty e)
{
/* Unlike listcomp, the inner function uses YIELD_VALUE instead of LIST_APPEND.
The outer scope wraps the call in RETURN_GENERATOR. */
compiler_comprehension(c, e, COMP_GENEXP, ...);
/* The nested function is a generator: it yields values */
ADDOP_I(c, YIELD_VALUE, 0);
ADDOP(c, RESUME, 0);
}

A generator expression is a comprehension function where each matching element is yielded rather than appended. The call site gets a generator object that lazily produces values.

gopy notes

compiler_comprehension is compile.compilerComprehension in compile/codegen_stmt_funclike.go. It uses compile.enterScope(COMPREHENSION). compiler_listcomp emits BUILD_LIST 0 then LIST_APPEND. compiler_genexp emits YIELD_VALUE and returns a generator via RETURN_GENERATOR.