nonlocal vs global
The gotcha
Assigning to a name inside a function creates a local variable, even if a same-named variable exists in an enclosing or global scope. To assign to the outer one, you must declare global (module-level) or nonlocal (enclosing function).
Minimal repro
x = 0
def f():
x = 1 # creates a NEW local x; module-level x is untouched
print(x) # 1
f()
print(x) # 0 ← unchanged
def outer():
x = 0
def inner():
x = 1 # NEW local in inner; outer's x untouched
inner()
print(x) # 0
Reading vs writing
Reading is easy — Python walks LEGB (Local → Enclosing → Global → Built-in):
x = 10
def f():
print(x) # 10 — reads from global, no declaration needed
Writing implicitly creates a local. The compiler decides at compile time which names are local based on whether any assignment to that name appears anywhere in the function body. Even an assignment in unreachable code makes the name local for the whole function:
x = 10
def f():
print(x) # UnboundLocalError — `x` is local because of the assignment below
if False:
x = 99
global — assign to module-level
counter = 0
def increment():
global counter
counter += 1
increment()
print(counter) # 1
Without global, counter += 1 is counter = counter + 1 → reads then assigns → local. The read fails because the local doesn’t have a value yet → UnboundLocalError.
nonlocal — assign to enclosing function scope
def make_counter():
n = 0
def increment():
nonlocal n
n += 1
return n
return increment
c = make_counter()
print(c(), c(), c()) # 1 2 3
Without nonlocal, the inner function creates its own n and the closure breaks. This pattern (counter, accumulator) is the classic motivation for nonlocal — added in Python 3.0.
What nonlocal does NOT reach
nonlocal only walks enclosing function scopes, not the module:
x = 10
def f():
nonlocal x # SyntaxError: no binding for nonlocal 'x' found
x = 20
For module-level, use global. For enclosing function, use nonlocal. There’s no syntax to skip levels — nonlocal binds to the nearest enclosing scope that has the name.
Common pitfalls
The “I just want to read the global” mistake:
config = {"debug": True}
def f():
config["verbose"] = True # no `global` needed — mutating the existing dict
config = {} # creates a new local; original untouched
Mutation works without global. Reassignment creates a local.
Closures over a loop variable:
funcs = []
for i in range(3):
funcs.append(lambda: i)
print([f() for f in funcs]) # [2, 2, 2] — not what you want
i is captured by reference, not value. See 02_late_binding_closures.md — nonlocal doesn’t fix this; default-argument or partial does.
Class bodies don’t behave like functions:
x = 1
class A:
x = 2
def f(self):
print(x) # 1 — class scope is NOT in LEGB
The class body is its own scope but methods can’t see it via LEGB. Use A.x or self.x.
When to use which
global— rarely. Module-level mutable state is usually a smell. Prefer passing values as arguments, or wrapping state in a class.nonlocal— closures over local state (counters, builders, decorators that accumulate).- Neither — mutate a container the outer scope owns (
config["x"] = 1). No declaration needed.
Interview angle
- Q: “What’s the difference between
globalandnonlocal?” — module-level vs enclosing function scope. - Q: “Why does
counter += 1raiseUnboundLocalErrorif there’s a globalcounter?” —+=is read+write; the assignment makes it local; the read fails. - Follow-up: “Can you read a global without declaring it?” — yes; only writes need the declaration.
- Follow-up: “What if you use
nonlocalfor a name not in any enclosing function?” —SyntaxErrorat compile time.
See 02_late_binding_closures.md, 12_comprehension_scope.md, 21_circular_imports.md.