Python Nonlocal in Closure: Scope Rules
python nonlocal in closure: Understand how the nonlocal keyword works in Python closures, when to use it, and how it differs from global and local scoping.
When a nested function needs to modify a variable from its enclosing function, Python requires the nonlocal statement. Without it, assignment inside the inner function creates a new local variable instead of updating the outer one. This article explains how python nonlocal in closure works, where it differs from global, and how to use it to manage state in closures.
What nonlocal Does in a Python Closure
A closure is a function that captures variables from the enclosing scope. In Python, when you define a function inside another function, the inner function can read variables from the outer function without any special declaration. Reading is always allowed. The problem appears when you try to assign a new value to one of those captured variables.
Consider this code:
def outer(): x = 10 def inner(): x = 20 # This creates a new local variable, not the outer x inner() print(x) # Still 10
The assignment x = 20 inside inner makes x a local variable of inner because Python determines variable scope statically. The outer x remains unchanged. The nonlocal statement changes this behavior. It tells Python that a variable should refer to a binding in the nearest enclosing scope (excluding the global scope). With nonlocal, assignment updates the outer variable.
def outer(): x = 10 def inner(): nonlocal x x = 20 inner() print(x) # 20
This is the core mechanism behind python nonlocal in closure. It allows a closure to maintain and modify state that persists between calls.
Minimal Example: Counting Calls with a Closure
A common use case is a counter that increments each time the closure is called. Without nonlocal, the increment operation fails because Python treats count as a local variable and raises UnboundLocalError when you try to read it before assignment.
def make_counter(): count = 0 def increment(): nonlocal count count += 1 return count return increment counter = make_counter() print(counter()) # 1 print(counter()) # 2
The nonlocal count declaration inside increment makes count refer to the variable in make_counter's scope. Each call to counter reads the current value, adds one, and stores it back. The closure holds the state, and nonlocal is the mechanism that allows the rebinding.
If you remove nonlocal, the same code raises UnboundLocalError because count += 1 is a read followed by a write, and the write makes count local. The read then finds no value assigned yet.
nonlocal vs global and local Scope
Python resolves names in a specific order: local, enclosing, global, built-in. The nonlocal keyword explicitly targets the enclosing scope, skipping the local scope. The global keyword targets the module-level scope. The difference matters when a variable exists both in an enclosing function and at the module level.
| Keyword | Scope targeted | Where allowed | Typical use |
|---|---|---|---|
| (none) | Local | Any function | Temporary variables |
nonlocal | Nearest enclosing function | Nested function | Modify captured closure state |
global | Module | Any function | Modify module-level variable |
If you use global inside a nested function to modify a variable that also exists in an enclosing function, you bypass the closure and modify the module-level variable instead. That is rarely what you want. Conversely, using nonlocal on a module-level variable raises a SyntaxError because there is no enclosing function scope.
x = 100 def outer(): x = 1 def inner(): nonlocal x # Refers to outer's x, not the module-level x x = 2 inner() print(x) # 2
In this example, nonlocal x binds to the x in outer, not the module-level x. The module-level x remains 100.
When a Closure Needs nonlocal
You need nonlocal when a closure must rebind a name from an enclosing scope. Rebinding means assigning a new object to that name. Common scenarios include:
- Counters or accumulators that update a numeric variable
- Caching a value that is recomputed lazily
- Changing a configuration flag inside a closure
- Implementing stateful decorators that track call counts
If the closure only mutates an object without reassigning the variable, nonlocal is unnecessary. For example, appending to a list does not rebind the list variable.
def make_accumulator(): items = [] def add(item): items.append(item) # No nonlocal needed; items is mutated, not reassigned return items return add
Here, items is captured by the closure, and append changes the list in place. The name items still points to the same list object. If you later wrote items = [] inside add, you would need nonlocal because that rebinds the name to a new list.
Common Mistakes and Their Symptoms
One frequent error is forgetting nonlocal and seeing UnboundLocalError when the closure tries to update a variable. The error message points to the line where the variable is read, which can be confusing because the assignment is elsewhere.
Another mistake is using global instead of nonlocal. This changes the wrong scope and can introduce subtle bugs where the closure modifies a module-level variable while the enclosing function's variable stays unchanged. The symptom is usually that the outer function's value remains the same, and the module-level value changes unexpectedly.
Using nonlocal where no enclosing binding exists raises a SyntaxError at compile time. For example:
def top(): nonlocal x # SyntaxError: no binding for nonlocal 'x' found
This happens because top is not nested inside another function. The same error occurs if you try to use nonlocal at module level.
A more subtle issue arises when the enclosing function has already returned. The closure still holds a reference to the variable, but you can only modify it through the closure. This is expected behavior, but it can make debugging harder because the state is not visible in the outer function's frame.
Runtime and Maintainability Considerations
Using nonlocal adds a small runtime overhead because the variable is accessed through a cell object rather than a fast local slot. In most applications this is negligible, but in tight loops inside a closure it can matter. The overhead is similar to accessing a variable from an enclosing function without nonlocal, which also uses cells.
Memory behavior is more significant. A closure that uses nonlocal keeps a reference to the variable's cell as long as the closure exists. If the outer function creates many closures that each capture large objects, those objects remain alive even after the outer function returns. This is the same behavior as any closure, but it becomes more visible when you intentionally use nonlocal to hold state.
From a maintainability perspective, nonlocal makes state implicit. A reader must trace the enclosing function to understand what variable is being modified. For complex state, a small class with explicit methods is often clearer. The class makes the state an explicit attribute and the methods make the transitions visible. Use nonlocal when the closure is simple and the state fits a single value or a small, well-understood set of values.
Advanced Usage: Nested Closures and Rebinding
nonlocal always refers to the nearest enclosing function scope. If you have multiple levels of nesting, you can use nonlocal in the innermost function to modify a variable from the immediately enclosing function, but not from a function further out unless you declare it in each intermediate level.
def outer(): x = 1 def middle(): def inner(): nonlocal x # Refers to outer's x x = 10 inner() middle() print(x) # 10
Here, inner is nested inside middle, but x is defined in outer. The nonlocal statement in inner searches outward and finds x in outer. It does not require a nonlocal declaration in middle because middle does not rebind x.
If middle also needed to rebind x, it would need its own nonlocal declaration. This can lead to multiple nonlocal statements in a chain, which is legal but can reduce readability.
A more advanced pattern is using nonlocal to implement a stateful decorator that counts how many times a function is called:
def count_calls(func): count = 0 def wrapper(*args, **kwargs): nonlocal count count += 1 print(f"Call {count}") return func(*args, **kwargs) return wrapper @count_calls def greet(name): return f"Hello, {name}" greet("Alice") greet("Bob")
The nonlocal count inside wrapper updates the counter that persists across calls. This pattern is common in profiling and debugging tools. It works because the closure captures the count cell, and nonlocal allows the rebinding.
When you combine nonlocal with mutable objects, you can build more complex state machines. For example, a closure that toggles between two values:
def make_toggle(): state = False def toggle(): nonlocal state state = not state return state return toggle t = make_toggle() print(t()) # True print(t()) # False
This works because nonlocal allows the rebinding of state to a new boolean object. The closure remains the only way to access and modify that state, which can be useful for encapsulating behavior without creating a class.
Understanding nonlocal is essential for writing correct closures in Python. It is the tool that turns a read-only closure into a stateful one. Use it deliberately, and prefer explicit classes when the state grows beyond a single variable.