Back to Blog
Python

Python Local vs Nonlocal Variable: Scope Explained

python local vs nonlocal variable: Understand how Python resolves local and nonlocal variables in nested functions, when to use nonlocal, and how it differs from global.

Pythonvariable scopenonlocalclosuresnested functions
Diagram showing local and nonlocal variable scope in Python nested functions

python local vs nonlocal variable requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

The distinction between local and nonlocal variables in Python often confuses developers who are new to nested functions. When you assign a variable inside a function, it becomes local to that function by default. But when you need to modify a variable from an enclosing function, you must explicitly declare it as nonlocal. This article explains the exact behavior, when to use nonlocal, and how it differs from global.

The Core Difference Between Local and Nonlocal Variables

In Python, a variable is considered local to a function if it is assigned anywhere in that function's body. This rule applies even if the assignment appears after a reference to the variable. For example:

def outer(): print(x) # UnboundLocalError x = 10

Python determines at compile time that x is local to outer, so the print statement tries to access a local variable that hasn't been assigned yet. This behavior is the foundation of the local vs nonlocal distinction.

A nonlocal variable, on the other hand, is a variable defined in an enclosing function but not in the global scope. It exists in the scope between the innermost function and the module level. The nonlocal keyword allows an inner function to bind to that variable and modify it.

How Python Resolves Variable Names in Nested Functions

When you nest functions, Python uses the LEGB rule to resolve names: Local, Enclosing, Global, Built-in. For a variable referenced inside an inner function, Python first checks the local scope of that function, then the enclosing scopes (from innermost outward), then the global scope, and finally the built-in scope.

Consider this example:

def outer(): message = "hello" def inner(): print(message) # reads from enclosing scope inner()

Here, inner can read message because it is found in the enclosing scope. This is a closure: the inner function captures the variable from the outer function. However, if inner tries to assign to message, it creates a new local variable unless nonlocal is used.

Using nonlocal to Modify an Enclosing Scope

The nonlocal statement tells Python that a variable refers to a binding in the nearest enclosing function scope. It must be used before any assignment to that variable in the inner function. Here is a minimal counter example:

def make_counter(): count = 0 def increment(): nonlocal count count += 1 return count return increment counter = make_counter() print(counter()) # 1 print(counter()) # 2

Without nonlocal, the count += 1 inside increment would create a local count and raise UnboundLocalError because it reads before assignment. The nonlocal keyword makes count refer to the count defined in make_counter, allowing the closure to mutate it across calls.

The same mechanism works for any nested function that needs to update a value from an enclosing scope. It is not limited to counters; it applies to accumulators, stateful decorators, and any pattern where an inner function must modify state that belongs to an outer function.

nonlocal vs global: When Each Applies

The global keyword modifies a variable in the module-level scope, while nonlocal modifies a variable in the nearest enclosing function scope. The two are not interchangeable. nonlocal cannot refer to a global variable, and global cannot refer to a variable in an enclosing function.

Here is a comparison:

KeywordScope it targetsExample usage
globalModule-levelglobal x in a function to modify a module variable
nonlocalEnclosing functionnonlocal x in a nested function to modify an outer function's variable

A common mistake is using global inside a nested function when the intent is to modify a variable from the outer function. That changes the module-level variable, not the enclosing one. Conversely, using nonlocal at the module level is a syntax error because there is no enclosing function scope.

Common Mistakes and Pitfalls

One frequent error is forgetting to declare nonlocal before assigning to a variable that exists in an enclosing scope. This leads to UnboundLocalError or, worse, silently creating a new local variable that shadows the outer one.

Another pitfall is using nonlocal on a variable that does not exist in any enclosing function. Python raises SyntaxError: no binding for nonlocal 'x' found. The variable must be defined in an outer function, not just in the global scope.

Also, nonlocal only works inside nested functions. If you attempt to use it in a function that is not nested, you get a syntax error. This is a common source of confusion when refactoring code from a nested structure to a flat one.

Finally, be aware that nonlocal binds to the variable in the nearest enclosing scope, not necessarily the outermost one. If there are multiple levels of nesting, nonlocal in the innermost function refers to the first enclosing function that defines the variable.

Practical Example: Stateful Closure with nonlocal

A realistic use case is a function that builds a reusable stateful callback. For instance, a rate limiter that tracks the last call time:

def rate_limiter(interval): last_call = 0 def allow(): nonlocal last_call import time now = time.time() if now - last_call >= interval: last_call = now return True return False return allow limit = rate_limiter(2) print(limit()) # True print(limit()) # False

Here, last_call is updated inside allow using nonlocal. Without it, the closure would not retain the updated value between calls, and the rate limiter would not work as intended.

This pattern is common in decorators that need to maintain per-function state, such as memoization caches or call counters. The nonlocal keyword makes the state explicit and avoids relying on mutable defaults or global variables.

Performance and Maintainability Considerations

Using nonlocal has negligible runtime cost; it is a compile-time binding that tells the interpreter where to find the variable. The performance impact is similar to accessing a local variable, though there is a slightly longer lookup chain because the interpreter must traverse the enclosing scope. In practice, this is not a bottleneck unless you are in a tight loop with millions of calls, and even then the difference is usually small.

From a maintainability perspective, nonlocal can make code harder to reason about because it creates hidden state that persists across calls. This is intentional in closures, but it can surprise developers who expect functions to be stateless. When you use nonlocal, document the state clearly and keep the closure small. If the state becomes complex, consider using a class instead.

A class with instance attributes often provides clearer state management than a closure with multiple nonlocal variables. For example, a counter can be implemented as:

class Counter: def __init__(self): self.count = 0 def increment(self): self.count += 1 return self.count

This is more verbose but makes the state explicit and easier to test. The choice between a closure with nonlocal and a class depends on the complexity of the state and the need for additional methods.

When to Avoid nonlocal

Avoid nonlocal when the state can be passed as an argument and returned as a result. Functional programming styles that avoid mutable state are often easier to debug and test. For example, instead of a closure that increments a counter, you can write a pure function that takes a counter and returns a new one.

Also, avoid nonlocal in recursive functions that need to accumulate results across calls. In such cases, passing the accumulator as an argument is clearer and avoids hidden state. The nonlocal keyword is best suited for situations where you need to create a stateful function on the fly, such as a decorator or a callback that must remember previous inputs.

Finally, be cautious when using nonlocal in multithreaded or asynchronous code. The state is shared across calls, and without proper synchronization, concurrent modifications can lead to race conditions. If you need thread safety, use a lock or consider a different design, such as a class with an explicit lock.

Understanding the distinction between local and nonlocal variables is essential for writing correct nested functions in Python. The nonlocal keyword gives you a controlled way to modify enclosing state, but it should be used deliberately and with a clear understanding of the scope rules involved.

python local vs nonlocal variable: Practical Usage and Code | RYUSLOG DEV