Using nonlocal is how Python lets a nested (inner) function modify variables from its enclosing (outer) function’s scope. Without nonlocal, assigning to an outer variable inside the inner function creates a new local variable instead — leading to the infamous UnboundLocalError or silent bugs. In 2026, nonlocal remains essential for closures, counters, factories, decorators, and any time you need persistent state without classes or globals.
Here’s a complete, practical guide to using nonlocal: how it works, when to use it, real patterns, pitfalls, and modern best practices with type hints and clarity.
Start with the basics. In this classic counter factory, the inner function needs to modify the outer count variable across calls. Without nonlocal, Python treats count += 1 as a local variable — causing an error on first increment.
def make_counter(start: int = 0) -> callable[[], int]:
"""Return a counter function that remembers and increments its count."""
count: int = start
def increment() -> int:
nonlocal count # Required to modify outer 'count'
count += 1
return count
return increment
counter = make_counter()
print(counter()) # 1
print(counter()) # 2
print(counter()) # 3
counter2 = make_counter(100)
print(counter2()) # 101
Without nonlocal, the same code fails:
def broken_counter():
count = 0
def increment():
count += 1 # UnboundLocalError: local variable 'count' referenced before assignment
return count
return increment
nonlocal tells Python: “Look up the chain for this name — don’t create a new local.” It works only for enclosing scopes (not global). For globals, use global — but avoid globals when possible.
Real-world pattern: custom multiplier factory. The returned function remembers the multiplier from creation time.
def make_multiplier(factor: float) -> callable[[float], float]:
"""Return a function that multiplies any input by the given factor."""
def multiply(x: float) -> float:
return x * factor
return multiply
double = make_multiplier(2.0)
triple = make_multiplier(3.0)
print(double(10)) # 20.0
print(triple(10)) # 30.0
Another common use: decorators that need to track state (e.g., call count).
def call_counter(func: callable) -> callable:
"""Decorator that counts how many times the function is called."""
count: int = 0
def wrapper(*args, **kwargs):
nonlocal count
count += 1
print(f"Call #{count} to {func.__name__}")
return func(*args, **kwargs)
return wrapper
@call_counter
def say_hello(name: str) -> str:
return f"Hello, {name}!"
print(say_hello("Alice")) # Call #1 to say_hello ? Hello, Alice!
print(say_hello("Bob")) # Call #2 to say_hello ? Hello, Bob!
Best practices make nested functions with nonlocal safe and readable. Always use type hints on both outer and inner functions — it clarifies intent and helps tools like mypy and IDEs. Write docstrings for the factory and the returned closure — describe what state is captured and how it behaves. Keep nesting shallow (1–2 levels) — deeper nesting hurts readability; extract helpers to top level if reused. Prefer closures over classes for simple state (counters, memoizers, accumulators) — they’re lighter and more Pythonic. Common pitfalls include forgetting nonlocal (causing UnboundLocalError or silent local creation) and overusing globals instead of passing parameters or returning values. In production, avoid relying on closures for critical shared state — use classes, dependency injection, or config objects for clarity and testability. Modern tip: combine closures with @cache or @lru_cache for memoized, efficient factories.
Mastering nonlocal unlocks powerful patterns: factories that customize behavior, closures that carry state, decorators that add functionality, and helpers that stay encapsulated. In 2026, use it thoughtfully with type hints, docstrings, and shallow nesting. When you need a function that remembers context or adapts on creation — return a nested function. It’s one of Python’s cleanest ways to write expressive, modular code.
Next time you need persistent state or customizable logic inside a function — reach for a nested function and nonlocal. You’ll write code that’s both powerful and elegant.