What is a closure in Python, and how does it capture variables from its enclosing scope?
A closure is a function object that remembers values in its enclosing lexical scope even when the scope has finished executing. In Python, when an inner function references a variable from an outer (enclosing) function, and the outer function returns the inner function, a closure is formed. The inner function 'closes over' the free variables from the enclosing scope.
Python implements closures through the __closure__ attribute, which holds cell objects that contain references to the captured variables. Importantly, closures capture references to variables, not their values at the time the closure is created. This means if the variable changes before the closure is called, the closure sees the updated value.
The __code__.co_freevars attribute lists the names of the free variables captured by the closure.
1. Define an outer function with a local variable
2. Define an inner function that references the outer variable
3. Return the inner function from the outer function
4. The returned function retains access to the outer variable
5. The outer function's local scope is preserved in __closure__
def make_multiplier(factor):
"""Outer function creates a closure over 'factor'."""
def multiplier(x):
return x * factor
return multiplier
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15
# Inspecting the closure
print(double.__closure__) # (<cell at 0x...>,)
print(double.__closure__[0].cell_contents) # 2
print(double.__code__.co_freevars) # ('factor',)
# Late binding demonstration
def make_functions():
funcs = []
for i in range(3):
def f():
return i # captures reference to i, not its value
funcs.append(f)
return funcs
results = [f() for f in make_functions()]
print(results) # [2, 2, 2] -- all see the final value of i
# Fix with default argument to capture current value
def make_functions_fixed():
funcs = []
for i in range(3):
def f(i=i): # default arg captures current value
return i
funcs.append(f)
return funcs
results = [f() for f in make_functions_fixed()]
print(results) # [0, 1, 2]
- •A closure captures references to variables, not copies of their values
- •The __closure__ attribute holds cell objects with captured variable references
- •Closures allow data encapsulation without classes
- •co_freevars lists the names of captured free variables
- •Late binding: closures in loops all share the same variable reference, so they all see the last value
- •Use default arguments (def f(i=i)) or functools.partial to capture current loop values
- •If the enclosing scope variable is reassigned, the closure sees the new value