Skip to main content

Python/symtable.c (part 5)

Source:

cpython 3.14 @ ab2d84fe1023/Python/symtable.c

This annotation covers scope resolution and the global/nonlocal statement effects. See python_symtable4_detail for the symbol table structure, analyze_block, and annotation flags.

Map

LinesSymbolRole
1-80_Py_MangleName mangling for __private attributes
81-180symtable_visit_stmt for GlobalRecord names as globally bound
181-280symtable_visit_stmt for NonlocalRecord names as free variables in enclosing scope
281-400analyze_cellsDetermine which variables need cell objects
401-600update_symbolsPropagate free variables up to the enclosing scope

Reading

_Py_Mangle

// CPython: Python/symtable.c:120 _Py_Mangle
PyObject *
_Py_Mangle(PyObject *privateobj, PyObject *ident)
{
/* Name mangling: __name (two leading underscores, at most one trailing)
becomes _ClassName__name. */
if (privateobj == NULL || !PyUnicode_Check(privateobj)) return ident;
const char *p = PyUnicode_AsUTF8(ident);
if (p[0] != '_' || p[1] != '_') return ident; /* Not private */
while (*p == '_') p++;
if (*p == '\0') return ident; /* Only underscores */
...
return PyUnicode_FromFormat("_%U%s", privateobj, p - 1);
}

class Foo: def __bar(self): pass stores the method as _Foo__bar. This makes unintentional override in subclasses harder. _Py_Mangle is called for every name in a class body during AST walking.

global statement effect

// CPython: Python/symtable.c:680 symtable_visit_stmt for Global
case Global_kind:
for (int i = 0; i < asdl_seq_LEN(s->v.Global.names); i++) {
PyObject *name = asdl_seq_GET(s->v.Global.names, i);
long cur = _PyST_GetSymbol(st->st_cur, name);
if (cur & (DEF_LOCAL | USE)) {
if (cur & DEF_LOCAL) {
PyErr_Format(PyExc_SyntaxError,
"name '%U' is assigned to before global declaration", name);
}
}
SET_SCOPE(st->st_cur, name, GLOBAL_EXPLICIT);
}
break;

global x marks x as GLOBAL_EXPLICIT in the current scope's symbol table. The compiler emits LOAD_GLOBAL/STORE_GLOBAL instead of LOAD_FAST/STORE_FAST. Using a name before declaring it global is a SyntaxError.

nonlocal statement effect

// CPython: Python/symtable.c:710 symtable_visit_stmt for Nonlocal
case Nonlocal_kind:
for (int i = 0; i < asdl_seq_LEN(s->v.Nonlocal.names); i++) {
PyObject *name = asdl_seq_GET(s->v.Nonlocal.names, i);
SET_SCOPE(st->st_cur, name, FREE);
st->st_cur->ste_free = 1; /* This scope has free variables */
}
break;

nonlocal x marks x as FREE in the current scope. update_symbols then walks up the enclosing scopes to find where x is bound and marks it as CELL there. The compiler emits LOAD_DEREF/STORE_DEREF for both sides.

analyze_cells

// CPython: Python/symtable.c:820 analyze_cells
static int
analyze_cells(PySTEntryObject *entry, PyObject *scopes, PyObject *bound)
{
/* A variable is a CELL if it is defined in this scope AND
referenced from an inner scope (i.e., it appears in 'bound'
of a nested scope). */
PyObject *name;
Py_ssize_t i = 0;
while (PyDict_Next(entry->ste_symbols, &i, &name, NULL)) {
long flags = _PyST_GetSymbol(entry, name);
if (!(flags & DEF_LOCAL)) continue;
if (!(flags & DEF_FREE_CLASS || PySet_Contains(bound, name)))
continue;
/* Mark as CELL */
SET_SCOPE(entry, name, CELL);
}
return 1;
}

A variable becomes a CELL when it is defined in the current scope and referenced from a nested scope. The cell wrapper allows the inner scope to reference the variable even after the outer function returns (closures).

gopy notes

_Py_Mangle is compile.Mangle in compile/compiler.go. symtable_visit_stmt for Global/Nonlocal sets flags in compile.SymbolScope. analyze_cells is compile.analyzeCells. update_symbols is compile.updateSymbols. The resulting scope information drives LOAD_FAST vs LOAD_GLOBAL vs LOAD_DEREF emission.