Python Closures Resolve Names When Called
Why closures created in loops share late-bound names, plus reliable capture patterns and nonlocal state.
A Python closure captures a name binding, not a snapshot of the object currently assigned to that name. When the inner function runs later, it reads the current contents of the enclosing scope’s closure cell. That is why functions created by one loop can all observe the loop variable’s final value.
This rule applies to def and lambda alike. It follows Python’s lexical name-resolution rules, documented for Python 3.14.7 in the execution model, and is illustrated directly in the Python programming FAQ.
A loop rebinds one name
def make_readers():
readers = []
for number in range(3):
readers.append(lambda: number)
return readers
readers = make_readers()
print([reader() for reader in readers])
The result is [2, 2, 2]. The loop does not create a new function scope on each iteration. All three lambdas close over the same number cell, and the last iteration leaves 2 in it.
You can inspect that shared cell in CPython and other implementations that expose __closure__:
assert readers[0].__closure__[0] is readers[1].__closure__[0]
This inspection is useful for learning and debugging. Application code should depend on the language’s scope rules, not on the layout of function internals.
Capture a value with a new binding
A default parameter is evaluated when the function definition executes. It therefore creates a separate stored value for each iteration:
def make_readers():
readers = []
for number in range(3):
readers.append(lambda number=number: number)
return readers
assert [reader() for reader in make_readers()] == [0, 1, 2]
Here the first number is a parameter local to the lambda. The second is the loop variable evaluated while that particular lambda is created. This is early capture through function-default semantics, not a special closure feature.
A factory function is often clearer when more than one value or some validation belongs to the capture:
def reader_for(number):
def read():
return number
return read
readers = [reader_for(number) for number in range(3)]
Every call to reader_for creates a new function scope and thus a different cell.
Comprehensions isolate the loop name, not each closure
In Python 3, a comprehension has its own implicit scope, so its iteration variable does not leak into the surrounding function. Closures created during one comprehension still share the comprehension’s one iteration-variable binding:
readers = [lambda: number for number in range(3)]
assert [reader() for reader in readers] == [2, 2, 2]
Use [lambda number=number: number for number in range(3)] when each function needs the value from its own iteration.
nonlocal deliberately updates the shared cell
Late binding is useful when the functions should share changing state. The nonlocal statement makes assignment target an existing name in the nearest enclosing function scope:
def make_counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
counter = make_counter()
assert [counter(), counter(), counter()] == [1, 2, 3]
Without nonlocal, count += 1 makes count local to increment, then tries to read that uninitialized local and raises UnboundLocalError. nonlocal requires a matching binding in an enclosing function scope and raises SyntaxError at compile time when none exists.
Mutation and rebinding are different
A closure can mutate a captured object without nonlocal because the name itself is only read:
def recorder():
events = []
def record(event):
events.append(event)
return tuple(events)
return record
Assigning events = [...] inside record would create a local name unless declared nonlocal. Calling events.append reads the enclosing binding and mutates the referenced list.
Choose the capture deliberately. Use a default parameter or factory when callbacks need per-iteration values. Keep a shared closure cell when later rebinding should be visible, and use nonlocal when the inner function owns that rebinding.