Scope, LEGB Rule & Closures
IntermediatePython resolves names through LEGB — Local, Enclosing, Global, Built-in — and inner functions capture enclosing variables as closures, the mechanism behind decorators.
Overview
When Python sees a name, it searches four layers in order: Local (current function), Enclosing (any outer function), Global (module), Built-ins (print, len...). Assignment inside a function creates a LOCAL name by default — even if a global with the same name exists — which produces the famous UnboundLocalError surprise. Closures are inner functions that remember variables from their enclosing scope even after the outer function has returned; they power decorators, callbacks and function factories. global and nonlocal exist to rebind outward — use them rarely.
LEGB & the UnboundLocalError Trap
Reading a global inside a function works. ASSIGNING to that name anywhere in the function makes it local for the WHOLE function — including lines before the assignment.
count = 0 # global
def show():
print(count) # OK — reads global (L? no, E? no, G? yes)
def bump_broken():
count += 1 # UnboundLocalError!
# assignment makes count LOCAL for the whole function,
# so count += 1 reads a local that doesn't exist yet
def bump():
global count # explicitly rebind the global
count += 1
bump(); bump()
print(count) # 2
# Prefer: pass values in, return values out — avoid global state
def bump_pure(c):
return c + 1Closures & nonlocal
The inner function keeps a live reference to the enclosing variable (a cell), not a copy. nonlocal lets the inner function REBIND the enclosing name — that's how stateful closures like counters work.
def make_counter():
count = 0
def increment():
nonlocal count # rebind enclosing, not global
count += 1
return count
return increment
c1 = make_counter()
c2 = make_counter() # independent state!
print(c1(), c1(), c1()) # 1 2 3
print(c2()) # 1
# Function factory — closure captures rate
def make_gst(rate):
def apply(amount):
return amount * (1 + rate / 100)
return apply
gst18 = make_gst(18)
print(gst18(1000)) # 1180.0
print(c1.__closure__[0].cell_contents) # 3 — the captured cell, visible!Key Points to Remember
- 1Name lookup order: Local → Enclosing → Global → Built-in (LEGB)
- 2Assignment anywhere in a function makes that name local everywhere in it — cause of UnboundLocalError
- 3Closures capture variables by reference (cells), not by value
- 4nonlocal rebinds an enclosing variable; global rebinds a module variable — both are code smells if frequent
Interview Questions
Sign in to ask AriaExplain the LEGB rule with an example where each level wins.
What is a closure? Build a counter without classes and explain why the state survives.
Why does x += 1 inside a function raise UnboundLocalError when x is global?
Ask Aria about Scope, LEGB Rule & Closures
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.