In Python, a closure is a function object that remembers values in enclosing scopes even if they are not present in memory. This feature allows you to create function-based objects that maintain internal state (like a counter) without writing a full class.
This guide explains how to create a closure that can perform multiple actions, specifically incrementing and decrementing a value, by utilizing the nonlocal keyword and returning multiple inner functions.
Understanding Scope and the nonlocal Keyword
To modify a variable from an outer (enclosing) function inside an inner function, you must explicitly declare it as nonlocal. Without this, Python treats the variable as local to the inner function, leading to errors when you try to modify immutable types like integers.
The Common Mistake
Attempting to modify a variable from the outer scope without nonlocal causes an UnboundLocalError.
def make_counter():
count = 0
def increment():
count += 1
return count
return increment
try:
counter = make_counter()
counter()
except UnboundLocalError as e:
print(f"Error: {e}")Output:
Error: cannot access local variable 'count' where it is not associated with a valueSolution
Use nonlocal to tell Python that count belongs to the enclosing scope.
def make_counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
counter = make_counter()
print(f"Count: {counter()}")Output:
Count: 1Method 1: Returning a Tuple of Functions
To support both incrementing and decrementing, the outer function needs to define two inner functions and return them both. Returning them as a tuple allows you to unpack them easily.
def create_counter(initial_value=0):
value = initial_value
def inc():
nonlocal value
value += 1
return value
def dec():
nonlocal value
value -= 1
return value
return inc, dec
increment, decrement = create_counter(10)
print(f"Increment: {increment()}") # 10 + 1
print(f"Increment: {increment()}") # 11 + 1
print(f"Decrement: {decrement()}") # 12 - 1Output:
Increment: 11
Increment: 12
Decrement: 11Method 2: Returning a Dictionary of Functions
If you have multiple operations (increment, decrement, reset, get_current), returning a tuple becomes confusing because you have to remember the order of the functions. Returning a dictionary is more scalable and explicit.
def create_advanced_counter(start=0):
count = start
def up():
nonlocal count
count += 1
return count
def down():
nonlocal count
count -= 1
return count
def reset():
nonlocal count
count = start
return count
return {
'inc': up,
'dec': down,
'reset': reset
}
actions = create_advanced_counter(5)
print(f"Up: {actions['inc']()}")
print(f"Up: {actions['inc']()}")
print(f"Down: {actions['dec']()}")
print(f"Reset: {actions['reset']()}")Output:
Up: 6
Up: 7
Down: 6
Reset: 5Closures vs. Classes
While closures are powerful, Python is an Object-Oriented language. It is often cleaner to use a Class for state management if the logic becomes complex.
When to use Closures:
- You need a simple callback function with a tiny bit of state.
- You want to hide data (encapsulation) strictly.
- You want to avoid the boilerplate of
class,self, and__init__.
When to use Classes:
- You need to manage complex state.
- You need inheritance or type checking.
- You require more than 2-3 methods to manipulate the data.
class Counter:
def __init__(self, start=0):
self.value = start
def inc(self):
self.value += 1
return self.value
def dec(self):
self.value -= 1
return self.value
c = Counter(10)
print(f"Class Increment: {c.inc()}")Output:
Class Increment: 11Conclusion
To define a closure that increments and decrements:
- Define an outer function that accepts an initial value.
- Define inner functions (
inc,dec) that use thenonlocalkeyword to modify the outer variable. - Return the inner functions as a tuple or dictionary.
- Unpack or access these functions to manipulate the shared state.