Python resolves a variable name using the LEGB rule: Local, Enclosing, Global, Built-in — in that order, the first match wins.
x = "global"
def outer():
x = "enclosing"
def inner():
x = "local"
print(x) # "local" — found in Local scope first
inner()
print(x) # "enclosing" — inner's assignment didn't touch outer's x
A closure is an inner function that remembers variables from its enclosing scope, even after the outer function has finished running:
def make_multiplier(factor):
def multiply(n):
return n * factor # "factor" is captured from make_multiplier's scope
return multiply
double = make_multiplier(2)
triple = make_multiplier(3)
double(5) # 10 — remembers factor=2 from when it was created
triple(5) # 15 — a completely separate closure, remembers factor=3
This is one of the most common "predict the output" interview questions in Python:
funcs = []
for i in range(3):
funcs.append(lambda: i)
[f() for f in funcs] # [2, 2, 2] — NOT [0, 1, 2]!
Each lambda closes over the variable i, not its value at creation time — by the time any lambda is actually called, the loop has finished and i is 2 for all three. The fix is to force evaluation at each iteration via a default argument (default values ARE evaluated immediately, at function-definition time — see the mutable default section below):
funcs = []
for i in range(3):
funcs.append(lambda i=i: i) # i=i captures the CURRENT value as a default
[f() for f in funcs] # [0, 1, 2] — correct
nonlocal and globaldef counter():
count = 0
def increment():
nonlocal count # without this, "count += 1" would raise UnboundLocalError
count += 1
return count
return increment
c = counter()
c() # 1
c() # 2
Without nonlocal, assigning to count inside increment would make Python treat count as a brand-new local variable in increment's own scope (because Python decides a variable's scope for the entire function body at compile time, based on whether it's assigned anywhere in that function) — and reading it before that local assignment raises UnboundLocalError, not a fallback to the outer count.