Python Closures
In this page:
def outer(outer_variable):
def inner():
return outer_variable
return inner
closure = outer(value)
What is a Closure?
A closure is a function that "remembers" the variables from the scope it was defined in, even after that outer function has already finished running and would otherwise have discarded them.
Example: What is a Closure?
def outer():
message = "Hi"
def inner():
print(message) # inner "remembers" message even after outer returns
return inner
closure = outer()
closure() # message is still accessible here
Preserving Outer State
Because a closure locks in specific values from its enclosing scope, you can use one to generate specialized helper functions on demand — like a make_multiplier(3) that returns a function which always multiplies by 3, without hardcoding that 3 everywhere it's needed.
Example: Preserving Outer State
def make_multiplier(factor):
def multiplier(x):
return x * factor # factor is captured from the enclosing call
return multiplier
times3 = make_multiplier(3) # locks in factor=3
print(times3(10))
Inspecting Closures
Every closure exposes a __closure__ attribute containing "cell" objects that hold references to the exact variables it captured, which is how Python keeps those values alive after the enclosing function has otherwise returned.
Example: Inspecting Closures
def make_multiplier(factor):
def multiplier(x):
return x * factor
return multiplier
times3 = make_multiplier(3)
print(times3.__closure__[0].cell_contents) # inspects the captured factor value directly
Dynamic State Modifications
The nonlocal keyword lets a closure not just read but actually modify a captured variable from its enclosing scope, which is how you can build a simple counter or accumulator using only nested functions instead of a class.
Example: Dynamic State Modifications
def make_counter():
count = 0
def counter():
nonlocal count # allows modifying the captured count variable
count += 1
return count
return counter
counter = make_counter()
print(counter()) # 1
print(counter()) # 2, count persists between calls
When to Use Closures
Closures are a lightweight alternative to defining a whole class when you only need to bundle one small piece of behavior with some remembered state — reach for a class instead once you need multiple related methods sharing that state.
Example: When to Use Closures
def make_counter(): # closure: one small behavior + state
count = 0
def counter():
nonlocal count
count += 1
return count
return counter
# A class would be overkill here for just one counter method
c = make_counter()
print(c(), c())
- Expecting a closure to capture a loop variable's value at creation time, so
[lambda: i for i in range(3)]all return 2 because they share onei. - Assigning to an outer variable inside the inner function without
nonlocal, which makes Python treat it as a new local and raiseUnboundLocalError. - Calling the outer function without calling the result, as in
counter = make_counterinstead ofmake_counter(), so you get the function itself and not the inner closure.
- Functions group reusable code, take parameters, and return values, with default arguments,
*args, and**kwargsadding flexibility. - Recursion, lambda functions, scope, and closures describe more advanced function behavior.
- Understanding scope and lifetime shows where variables can be used.
Chapter Quiz — Complete all 9 topics to unlock
0/9 topics done
Complete these topics first: