Python Nonlocal Variable: Scope and Usage
Understand the python nonlocal variable keyword, its role in nested functions, and practical examples for closures.
When you need to modify a variable from an enclosing function inside a nested function, Python's scoping rules will raise an UnboundLocalError unless you declare the variable with nonlocal. The python nonlocal variable keyword tells the interpreter that the name refers to a variable in the nearest enclosing scope, not a new local variable. Without this declaration, any assignment inside the nested function creates a new local name, which shadows the outer variable and often leads to confusing bugs.
The Problem nonlocal Solves
Consider a nested function that tries to update a counter defined in its parent function:
def counter(): count = 0 def increment(): count += 1 return count return increment inc = counter() print(inc()) # UnboundLocalError: local variable 'count' referenced before assignment
The error occurs because Python determines variable scope at compile time. When the count += 1 assignment appears in increment, Python marks count as a local variable for that function. The subsequent read of count in the same expression then finds no local value yet, because the assignment hasn't executed. The nonlocal declaration overrides this default by telling Python that count belongs to an enclosing scope.
How nonlocal Differs from global
nonlocal and global both allow assignment to names defined outside the current function, but they target different scopes. global refers to the module-level namespace, while nonlocal refers to the nearest enclosing function scope that defines the name. This distinction matters when you have multiple levels of nesting.
def outer(): x = 10 def inner(): nonlocal x x += 1 return x return inner
Here nonlocal x makes x refer to the variable in outer. If you used global x instead, Python would look for x at the module level, which may not exist or would be a different variable entirely. Using nonlocal keeps the modification confined to the closure, which is usually what you want when building stateful functions.
A Minimal Working Example
A classic use case is a closure that maintains state without creating a class. The following example implements a simple counter using nonlocal:
def make_counter(): value = 0 def increment(step=1): nonlocal value value += step return value return increment counter = make_counter() print(counter()) # 1 print(counter(5)) # 6
The nonlocal declaration allows increment to read and modify value from make_counter's scope. Each call to make_counter creates a fresh value variable, so independent counters do not interfere with each other. This pattern is a lightweight alternative to a class when you only need a single function with persistent state.
Common Mistakes and Edge Cases
A frequent mistake is forgetting that nonlocal must appear before any use of the variable in the function body. The declaration must be placed at the top of the function, typically right after the docstring, to avoid a SyntaxError. Another edge case arises when the enclosing scope does not actually define the variable. If you declare nonlocal for a name that does not exist in any enclosing function scope, Python raises a SyntaxError at compile time.
def outer(): def inner(): nonlocal missing # SyntaxError: no binding for nonlocal 'missing' found missing = 1 return inner
Also, nonlocal cannot be used at the module level because there is no enclosing function scope. Attempting to do so results in a SyntaxError. The variable must be defined in an enclosing function, not just at the module level.
When to Use nonlocal vs Mutable Containers
Some developers avoid nonlocal by using a mutable container, such as a list or dictionary, to hold the state. For example:
def make_counter(): state = {'value': 0} def increment(): state['value'] += 1 return state['value'] return increment
This works because mutating the dictionary does not rebind the name state, so no nonlocal is needed. The choice between nonlocal and a mutable container depends on clarity and intent. nonlocal directly expresses that you are modifying a variable in an enclosing scope, which is more explicit. A mutable container can be useful when you need to share several pieces of state or when the variable name itself should not be reassigned. However, using a container solely to avoid nonlocal often obscures the code's purpose.
Performance and Maintainability Considerations
nonlocal has negligible runtime overhead; the lookup is resolved at compile time and uses cell variables, which are slightly slower than local variable access but comparable to closure access. The real cost is maintainability. Overusing nonlocal can make functions harder to reason about because the state is hidden in enclosing scopes. When a closure grows complex, consider whether a class with an explicit __init__ and methods would be clearer. Classes make state visible and testable, while nonlocal keeps state implicit. For simple counters or one-off stateful callbacks, nonlocal is concise and appropriate. For larger state machines or when multiple methods need access to the same state, a class is usually a better fit.
Compatibility and Version Notes
The nonlocal keyword was introduced in Python 3. It is not available in Python 2, which relied on mutable containers or global variables for similar behavior. If you are maintaining code that must run on both Python 2 and 3, you cannot use nonlocal directly. In modern Python 3 code, nonlocal is fully supported and widely used in closures, decorators, and factory functions. When porting legacy code, be aware that converting a nonlocal usage to a mutable container is a straightforward migration, but it changes the code's structure and may require updating comments and tests.
A final technical detail: nonlocal works with any enclosing function scope, not just the immediate parent. If you have three levels of nesting, nonlocal will bind to the nearest enclosing scope that defines the name. This allows you to skip intermediate scopes that do not have the variable. Understanding this resolution rule helps you predict which variable gets modified when you have deeply nested functions, and it prevents accidental shadowing in complex codebases.