Scope analysis in Python/symtable.c: local, global, free, and cell variable classification before compilation.
After parsing produces an AST, CPython performs scope analysis.
This stage builds symbol tables.
A symbol table records how names behave inside each scope. It determines whether a name is local, global, free, cell, parameter, imported, annotated, or referenced from nested scopes.
For this source:
x = 10
def outer():
y = 20
def inner():
return x + y
return innerthe parser only knows that names appear in expressions and assignments.
The symbol table phase determines:
x global from inner
y free variable in inner
y cell variable in outer
inner local variable in outer
outer global variable in moduleThis analysis is essential because bytecode generation depends on it. Loading a local variable uses different bytecode from loading a global or closure variable.
21.1 Position in the Compilation Pipeline
The symbol table phase sits between AST construction and bytecode generation.
source
↓
tokenization
↓
parsing
↓
AST
↓
symbol table analysis
↓
compiler
↓
code object
↓
bytecode executionThe parser builds syntax structure.
The symbol table phase builds scope structure.
The compiler then uses both.
21.2 What a Symbol Table Contains
Each scope has a symbol table.
A symbol table stores information such as:
which names exist
where names are assigned
where names are read
whether names are parameters
whether names are imported
whether names are global
whether names are nonlocal
whether names escape into nested scopes
whether names require closure cellsConceptually:
Scope: function outer
Symbols:
y local + cell
inner localScope: function inner
Symbols:
x global
y freeThe symbol table phase does not execute code. It performs static scope classification.
21.3 Python Uses Lexical Scoping
Python uses lexical scoping.
A nested function can access names from enclosing scopes.
Example:
def outer():
x = 10
def inner():
return x
return innerinner can access x because x exists in the enclosing lexical environment.
The symbol table phase determines this relationship before runtime execution.
Without lexical scope analysis, CPython would not know:
where to look for a name
whether to allocate closure storage
whether a name belongs in locals
whether a variable escapes into nested functions21.4 Scopes in Python
Python has several scope types.
| Scope | Example |
|---|---|
| Module scope | top-level file |
| Function scope | def f(): |
| Class scope | class C: |
| Comprehension scope | [x for x in xs] |
| Generator scope | (x for x in xs) |
| Lambda scope | lambda x: x + 1 |
Each scope has independent symbol analysis.
Example:
x = 1
def f():
x = 2
return xThe module and function each have their own x.
The symbol table phase separates them.
21.5 Module Scope
Top-level code executes in module scope.
Example:
x = 1
y = 2
def add():
return x + yAt module scope:
x global/module-local
y global/module-local
add global/module-localInside add:
x global
y globalAt runtime, globals are stored in the module dictionary.
The symbol table phase determines that references inside add should use global lookup bytecode rather than local lookup bytecode.
21.6 Function Scope
Function bodies create local scopes.
Example:
def f(a, b):
c = a + b
return cInside f:
a parameter + local
b parameter + local
c localParameters are treated as initialized local variables.
The compiler later allocates slots for locals inside the frame object.
The symbol table phase determines:
how many local variables exist
which names are parameters
which bytecode instructions to emit21.7 Local Variables
A name assigned anywhere in a function becomes local unless declared otherwise.
Example:
x = 10
def f():
print(x)
x = 20This raises:
UnboundLocalErrorWhy?
Because the symbol table phase sees assignment to x inside f:
x = 20That means:
x is local to fSo earlier:
print(x)tries to read the local x before assignment.
This behavior comes from static scope analysis, not runtime guessing.
21.8 Global Declarations
global changes scope classification.
Example:
x = 10
def f():
global x
x = 20Now the symbol table records:
x globalinside f.
The assignment updates the module-level variable.
Without global, assignment would create a local variable.
The symbol table phase must process global declarations before compiling name accesses.
21.9 Nonlocal Declarations
nonlocal refers to enclosing function scope variables.
Example:
def outer():
x = 10
def inner():
nonlocal x
x += 1
inner()
return xInside inner:
x free variableInside outer:
x cell variableA cell variable is a local variable captured by nested scopes.
Without nonlocal, assignment inside inner would create a new local variable.
nonlocal tells the symbol table:
do not create a local binding
use enclosing function binding21.10 Free Variables
A free variable is a name used inside a scope but defined in an enclosing function scope.
Example:
def outer():
x = 10
def inner():
return xInside inner:
x free variablex is not local to inner.
It comes from outer.
Free variables require closure support.
21.11 Cell Variables
A cell variable is a local variable referenced by nested scopes.
Example:
def outer():
x = 10
def inner():
return xInside outer:
x local + cellWhy?
Because x must survive after outer returns.
The nested function still needs access to it.
CPython stores captured variables in heap-allocated closure cells rather than ordinary stack-only locals.
The symbol table phase determines which locals need cell storage.
21.12 Closures
Closures depend directly on symbol table analysis.
Example:
def make_counter():
count = 0
def inc():
nonlocal count
count += 1
return count
return incThe symbol table determines:
count in make_counter:
local + cell
count in inc:
freeThe compiler then generates closure machinery.
At runtime:
c = make_counter()returns a function carrying access to the captured count cell.
Without symbol table analysis, closure construction would be impossible.
21.13 Name Resolution Categories
CPython broadly classifies names into categories.
| Category | Meaning |
|---|---|
| Local | Defined in current scope |
| Global explicit | Declared with global |
| Global implicit | Resolved at module/builtin level |
| Free | Comes from enclosing scope |
| Cell | Local captured by nested scope |
This classification controls bytecode generation.
Example bytecode categories:
| Category | Bytecode family |
|---|---|
| Local | LOAD_FAST |
| Global | LOAD_GLOBAL |
| Free/cell | LOAD_DEREF |
| Name lookup | LOAD_NAME |
Different scope decisions produce different runtime behavior and performance.
21.14 Bytecode Depends on Scope Analysis
Example:
def f(a):
return aBytecode:
LOAD_FAST a
RETURN_VALUEa is local.
Now:
x = 1
def f():
return xBytecode:
LOAD_GLOBAL x
RETURN_VALUENow closure case:
def outer():
x = 1
def inner():
return xInside inner:
LOAD_DEREF x
RETURN_VALUEThe parser alone cannot determine these instructions.
The symbol table phase provides the required scope information.
21.15 Class Scope
Class bodies have special scope behavior.
Example:
x = 1
class C:
y = xInside the class body:
y local to class namespace
x globalClass bodies execute in their own namespace dictionary.
But methods do not automatically capture class-scope variables lexically.
Example:
class C:
x = 1
def f(self):
return xThis does not access C.x.
The name x inside f is treated as global unless otherwise defined.
Class scope differs from function scope in important ways.
21.16 Comprehension Scopes
Modern Python comprehensions create their own scope.
Example:
x = 100
values = [x for x in range(3)]
print(x)Output:
100The comprehension variable does not leak into outer scope.
Internally:
comprehension creates nested scope
x inside comprehension is local there
outer x remains unchangedEarlier Python versions behaved differently. Modern CPython uses isolated comprehension scopes.
21.17 Generator Expression Scopes
Generator expressions also create nested scopes.
Example:
x = 10
g = (x for x in range(3))
print(x)Outer x remains unchanged.
The generator expression behaves similarly to an implicit nested function.
The symbol table phase creates separate symbol information for generator scopes.
21.18 Lambda Scopes
Lambda expressions create function scopes.
Example:
x = 10
f = lambda y: x + yInside the lambda:
y local parameter
x free variableLambdas are anonymous functions, but symbol table analysis treats them similarly to ordinary nested functions.
21.19 Imports and Symbol Tables
Imports also affect symbol tables.
Example:
import os
from math import sinModule scope symbols:
os imported local/global
sin imported local/globalInside functions:
def f():
import jsonjson becomes local to f.
Imports are assignments from the perspective of scope analysis.
21.20 Annotations
Variable annotations participate in symbol handling.
Example:
x: int = 1The symbol table records:
x assignedFunction annotations also appear:
def f(x: int) -> str:
return str(x)The symbol table phase tracks annotation-related scope usage, especially because annotations may reference names.
21.21 Exception Handler Variables
Exception handler names create local bindings.
Example:
try:
run()
except ValueError as e:
print(e)Inside the exception block:
e localCPython later clears exception variables after the handler to avoid reference cycles involving tracebacks.
The symbol table phase identifies the binding itself. Cleanup behavior occurs later.
21.22 Pattern Matching Bindings
Pattern matching introduces binding rules.
Example:
match value:
case [x, y]:
print(x, y)Pattern variables become local bindings.
The symbol table phase must distinguish:
pattern capture
ordinary expressionbecause pattern syntax has different semantics.
21.23 AST Traversal During Symbol Analysis
The symbol table phase walks the AST.
Simplified model:
visit Module
create module scope
visit FunctionDef
register function name
create child scope
visit parameters
visit body
visit Assign
mark targets as assigned
visit expression
visit Name
mark as load/store/delete
visit Global
record explicit global declaration
visit Nonlocal
record nonlocal declarationThe traversal collects information first, then resolves scope relationships.
21.24 Two-Pass Nature of Scope Analysis
Scope analysis is not purely local.
Example:
def f():
print(x)
x = 1The meaning of the first x depends on later assignment.
Therefore CPython cannot classify names in a single left-to-right pass.
The symbol table phase effectively gathers scope information across the entire block before final classification.
21.25 Nested Scope Trees
Scopes form a tree.
Example:
x = 0
def outer():
y = 1
def inner():
z = 2Scope tree:
Module
outer
innerEach scope stores links to child scopes.
Free variable resolution walks outward through enclosing scopes.
21.26 Builtins
If a name is neither local nor global module-defined, runtime lookup may fall back to builtins.
Example:
print(len([1, 2, 3]))Inside module scope:
print implicit global/builtin
len implicit global/builtinThe symbol table phase does not resolve actual builtin objects. It classifies lookup style.
At runtime, global lookup checks:
module globals
then builtins21.27 The symtable Module
Python exposes symbol tables through the symtable module.
Example:
import symtable
src = """
x = 1
def outer():
y = 2
def inner():
return x + y
"""
table = symtable.symtable(src, "<input>", "exec")
print(table.get_identifiers())Nested symbol tables can be inspected programmatically.
This module is useful for:
linters
static analyzers
teaching scope behavior
compiler experiments21.28 Symbol Table Data Structures in CPython
Important source areas include:
Python/symtable.c
Include/internal/
Python/compile.cThe symbol table machinery stores:
scope type
symbol flags
child scopes
free variables
cell variables
parameter information
optimization flagsSymbol flags describe behavior such as:
assigned
used
parameter
global
nonlocal
free
cell
imported
annotatedThe compiler later consumes this metadata.
21.29 Optimization and Locals
Scope analysis enables fast local variable access.
Local variables use indexed slots inside frames.
Example:
def f(a, b):
c = a + b
return cLocals can be compiled into array-style accesses:
locals[0] -> a
locals[1] -> b
locals[2] -> cThis is much faster than dictionary lookup.
Without symbol table analysis, CPython could not precompute local layouts.
21.30 Minimal Mental Model
Use this model:
The parser builds syntax structure.
The symbol table phase builds scope structure.
Each module, function, class, lambda, and comprehension creates a scope.
Names are classified as local, global, free, or cell variables.
Nested functions create closure relationships.
The compiler uses symbol table information to generate correct bytecode.
Fast locals, globals, and closures all depend on symbol analysis.The symbol table phase is where CPython turns syntax into executable scope semantics.