Python closures, explained
A closure is a function that remembers variables from where it was defined, even after that enclosing scope has returned. Closures are the foundation of decorators, callbacks, and many functional patterns — and the late-binding loop trap is a perennial interview favourite.
What a closure is
A closure happens when a nested function references a variable from its enclosing function and is then returned (or otherwise outlives that function). The variable it "closes over" is called a free variable:
def make_multiplier(factor):
def multiply(n):
return n * factor # factor is a free variable
return multiply
double = make_multiplier(2)
triple = make_multiplier(3)
double(5) # 10
triple(5) # 15
Even though make_multiplier has returned, double still remembers factor == 2. Each
call to make_multiplier creates an independent closure with its own captured state.
Inspecting the captured variables
The captured values live on the function object's __closure__, and the names on
__code__.co_freevars:
double.__code__.co_freevars # ('factor',)
double.__closure__[0].cell_contents # 2
You rarely need this, but it proves the value is genuinely stored with the function, not recomputed.
Closures capture variables, not values
This is the crucial subtlety: a closure stores a reference to the variable, and reads its current value when called, not when defined. With a single value that's invisible; in a loop it causes the classic bug:
funcs = []
for i in range(3):
funcs.append(lambda: i)
[f() for f in funcs] # [2, 2, 2] — all share the same i, now 2
All three lambdas close over the same i, which is 2 after the loop. The fix is to bind
the current value with a default argument (evaluated at definition time):
funcs = [lambda i=i: i for i in range(3)]
[f() for f in funcs] # [0, 1, 2]
Using functools.partial(lambda i: i, i) works too — the point is to snapshot the value
each iteration.
nonlocal — writing to a captured variable
By default a closure can read a free variable but not rebind it (assignment would make
it local). nonlocal lets the inner function modify the enclosing variable, which is how
you keep mutable state in a closure:
def make_counter():
count = 0
def increment():
nonlocal count # rebind the enclosing count
count += 1
return count
return increment
c = make_counter()
c(), c(), c() # (1, 2, 3)
Without nonlocal, count += 1 raises UnboundLocalError because the assignment makes
count local to increment.
Closures vs classes
A closure with state is essentially a tiny object with one method. When do you pick which?
# Closure — concise when there's a single behaviour and a little state
def make_adder(n):
return lambda x: x + n
# Class — clearer when there are multiple methods or richer state
class Adder:
def __init__(self, n):
self.n = n
def __call__(self, x):
return x + self.n
def reset(self):
...
Use a closure for a single, simple captured behaviour (a callback, a configured function). Use a class when you need multiple related methods, introspectable state, or the object will grow.
Recap
A closure is a nested function that remembers free variables from its enclosing scope,
giving each instance independent captured state — the basis of decorators and callbacks.
Closures capture variables, not values, so functions built in a loop all see the final
value unless you snapshot it with a default argument. Use nonlocal to rebind a
captured variable (e.g. a running counter). Choose a closure for one simple behaviour and a
class when state and methods grow.