22.5 Practical Closure Patterns: Factories and Counters
The Factory Pattern with Closures
A factory function is a function that returns another function, often customizing the returned function’s behavior based on the arguments passed to the factory. Closures are the fundamental mechanism that makes this pattern so powerful and memory-efficient in Python. The inner function “closes over” the variables from the factory’s scope, preserving them for the lifetime of the returned function object. This allows each returned function to maintain its own unique state without relying on global variables or class-based constructs.
Consider a simple factory that creates greeting functions personalized for a specific event, like a birthday or holiday.
def create_greeter(greeting_word):
"""A factory function that creates a greeter function."""
def greeter(name):
# The inner function greeter has access to the 'greeting_word' variable
# from the enclosing scope of create_greeter.
print(f"{greeting_word}, {name}!")
return greeter
# Create two specialized greeter functions
birthday_greeter = create_greeter("Happy Birthday")
holiday_greeter = create_greeter("Merry Christmas")
# Each function remembers its own 'greeting_word'
birthday_greeter("Alice") # Output: Happy Birthday, Alice!
holiday_greeter("Bob") # Output: Merry Christmas, Bob!
The greeting_word for each function ('Happy Birthday' and 'Merry Christmas') is stored within the closure of each function. This is more elegant and encapsulated than using a class for such a simple task.
Implementing Stateful Counters
One of the most classic uses of closures is to create function objects that maintain a persistent state between calls. This is ideal for implementing counters, where the count must be remembered each time the function is invoked. The closure provides a private, namespaced state for each counter, preventing accidental interference from other parts of the code.
def create_counter(start=0):
"""Creates a counter function that increments and returns its count."""
count = start # This variable is captured by the closure
def counter():
nonlocal count # Crucial for modifying the captured variable
current = count
count += 1
return current
return counter
# Create two independent counters
counter_a = create_counter()
counter_b = create_counter(10)
print(counter_a()) # Output: 0
print(counter_a()) # Output: 1
print(counter_b()) # Output: 10
print(counter_a()) # Output: 2
The Crucial Role of nonlocal
In the counter example above, the nonlocal statement is not just important—it is essential. Without it, the code would fail or behave incorrectly. Here’s why:
When you assign a value to a variable inside a function, Python treats that variable as local to that function’s scope by default. If you write count += 1 without declaring nonlocal count, Python sees this as an assignment and creates a new local variable named count. It then tries to increment this new local variable before it has been assigned a value, leading to an UnboundLocalError.
The nonlocal keyword explicitly tells the Python interpreter: “Do not create a new local variable; instead, look for this variable in the nearest enclosing scope that is not global and bind to it.” This allows the inner counter function to rebind the variable count that exists in the create_counter scope, thereby updating the state that is preserved within the closure.
def create_broken_counter():
count = 0
def counter():
# Missing 'nonlocal' declaration
count += 1 # This causes UnboundLocalError
return count
return counter
broken_counter = create_broken_counter()
# broken_counter() # UnboundLocalError: local variable 'count' referenced before assignment
Pitfalls and Best Practices
A common pitfall arises when the closed-over variable is mutable, like a list, and the factory function’s arguments are mutated in a loop. Since the closure binds to the variable itself, not its value, all inner functions can end up referencing the same final value of the loop variable.
def create_multipliers_bad():
multipliers = []
for i in range(5):
# The closure binds to the variable `i`, not its current value.
multipliers.append(lambda x: i * x)
return multipliers
for m in create_multipliers_bad():
print(m(2)) # Prints 8, 8, 8, 8, 8 (all use the final value of i: 4)
The solution is to capture the value at the time the function is created by using a default argument or by passing the value as an argument to a nested scope.
def create_multipliers_good():
multipliers = []
for i in range(5):
# Default arguments are evaluated at function definition time,
# capturing the current value of `i`.
multipliers.append(lambda x, cap_i=i: cap_i * x)
return multipliers
for m in create_multipliers_good():
print(m(2)) # Correctly prints 0, 2, 4, 6, 8
Best Practice: Use closure factories for creating small, focused stateful functions. They provide excellent encapsulation and avoid the syntactic overhead of a class. However, if the state becomes complex or requires multiple methods to manipulate it, switching to a class (which bundles both state and behavior) is often a more maintainable and Pythonic choice. Closures excel at simplicity and lightweight state management.