Closures are a powerful mechanism in Python that allows creating functions with "remembered" data from the surrounding context.
In functional languages and in Python, functions are first-class objects. A function can return a new function that 'closes over' variables from its surrounding (outer) scope.
Developers often get confused about how exactly closed-over variables "live" inside a closure when they are read or written, how to work with them safely (especially with mutable objects).
If an inner function uses variables from the outer function, they are automatically "remembered" — even if the outer function has completed execution. Reading a variable is straightforward, but if you need to change its value, you must use the nonlocal keyword. When working with mutable objects (lists, dicts), this is a particular risk area.
Example:
def outer(): count = 0 def inner(): nonlocal count count += 1 return count return inner counter = outer() print(counter()) # 1 print(counter()) # 2
Key Features:
nonlocal (otherwise, the operation creates a local variable).Can you modify the value of a closed-over variable inside a function without nonlocal?
No. If you attempt to assign a new value without specifying nonlocal, Python interprets this as creating a new local variable, and the old value won't be accessible outside.
Example:
def make_counter(): count = 0 def inner(): count += 1 # UnboundLocalError! return count return inner
Can you pass arguments to the outer scope through a closure?
Yes, a closure will "remember" any variables that are accessible in the outer scope, including class attributes, global variables, and so forth. However, modifying these variables requires special efforts (for instance, using nonlocal or global).
What happens with mutable objects inside a closure?
If a closed-over variable holds a reference to a mutable object, such as a list, you can modify its contents without nonlocal, but if you try to reassign that variable, nonlocal will be required.
Example:
def make_appender(): result = [] def append(x): result.append(x) # Allowed! return result return append f = make_appender() print(f(1)) # [1] print(f(2)) # [1, 2]
A developer writes a closure, modifies a variable without nonlocal — an UnboundLocalError arises.
Pros:
Cons:
Explicit usage of nonlocal for controlled state in a closure.
Pros:
Cons: