aosabook/500lines · error · UnboundLocalError
local variable '%s' referenced before assignment
Error message
local variable '%s' referenced before assignment
What it means
Raised by byte_LOAD_FAST when a name the compiler classified as a fast local is missing from frame.f_locals at the point of the load. Because a function assigns to the variable somewhere in its body, the compiler made it local and emitted LOAD_FAST/STORE_FAST; the interpreter consults ONLY f_locals (no globals/builtins fallback). This reproduces CPython's UnboundLocalError when a branch reads the local before the assignment runs.
Source
Thrown at interpreter/code/byterun/simple_python_interpreter.py:245
elif name in frame.f_globals:
val = frame.f_globals[name]
elif name in frame.f_builtins:
val = frame.f_builtins[name]
else:
raise NameError("name '%s' is not defined" % name)
self.push(val)
def byte_STORE_NAME(self, name):
self.frame.f_locals[name] = self.pop()
def byte_DELETE_NAME(self, name):
del self.frame.f_locals[name]
def byte_LOAD_FAST(self, name):
if name in self.frame.f_locals:
val = self.frame.f_locals[name]
else:
raise UnboundLocalError(
"local variable '%s' referenced before assignment" % name
)
self.push(val)
def byte_STORE_FAST(self, name):
self.frame.f_locals[name] = self.pop()
def byte_LOAD_GLOBAL(self, name):
f = self.frame
if name in f.f_globals:
val = f.f_globals[name]
elif name in f.f_builtins:
val = f.f_builtins[name]
else:
raise NameError("global name '%s' is not defined" % name)
self.push(val)
## OperatorsView on GitHub (pinned to fba689d101)
Solutions
- Initialize the local before any read (assign it at the top of the function or on every branch).
- If the global was intended, declare `global name` so the compiler emits LOAD_GLOBAL instead of LOAD_FAST.
- Restructure the logic so the STORE_FAST always precedes the LOAD_FAST on every reachable path.
Example fix
// before
def f(flag):
if flag: print(x) # UnboundLocalError
x = 1
// after
def f(flag):
x = 0
if flag: print(x) Defensive patterns
Strategy: validation
Validate before calling
# static check: every LOAD_FAST name has a STORE_FAST that dominates it
import dis
fast_reads = {i.argval for i in dis.get_instructions(code) if i.opname == 'LOAD_FAST'}
fast_writes = {i.argval for i in dis.get_instructions(code) if i.opname == 'STORE_FAST'}
unsafe = fast_reads - fast_writes # names read but never assigned
# at runtime, guard the read:
if name in frame.f_locals:
val = frame.f_locals[name]
else:
val = <default> Try / catch
try:
interpreter.run_frame(frame)
except UnboundLocalError as e:
log.warning('local read before assign: %s', e) Prevention
- Initialize locals at the top of every function that conditionally assigns them.
- Use `global name` when you intend a module-level binding.
- Run a static analyzer (pylint W0612/unbound) to catch read-before-write paths.
When it happens
Trigger: A function that assigns to `name` (forcing LOAD_FAST) but a code path reads `name` before any STORE_FAST runs, e.g. `def f(flag):\n if flag: print(x)\n x = 1`. Also from augmented assignment or conditional initialization that a skipped branch never executes.
Common situations: Adding a later `x = ...` inside a function that silently turns a previously-global `x` into a local; conditional initialization where one branch skips the assignment; refactor that moves an assignment inside an `if`.
Related errors
AI-assisted analysis of aosabook/500lines@fba689d101 (2026-08-13).
Data as JSON: /api/errors/b425434cf34a413c.
Report an issue: GitHub.