Closures and nonlocal in Python
What is a Closure?
A closure is a function that remembers variables from the scope where it was created, even after that outer function has finished running.
Closures allow functions to keep a private internal state without using classes or global variables.
Basic Closure Example
def outer():
message = "Hello"
def inner():
return message
return inner
func = outer()
print(func())
Output
Hello
Why Does This Work?
Normally, local variables disappear after a function ends.
But here:
message = "Hello"
is remembered by:
inner()
Even after outer() has finished.
That remembered environment is called a:
→ Closure
Lexical Scoping
Python uses lexical scoping.
This means: - inner functions can access variables from outer scopes - variable lookup depends on where the function was defined
Scope Order (LEGB Rule)
Python searches variables in this order:
| Scope | Meaning |
|---|---|
| Local | Current function |
| Enclosing | Outer function |
| Global | File-level variables |
| Built-in | Python built-ins |
Closure Memory Example
def counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
c = counter()
print(c())
print(c())
print(c())
Output
1
2
3
Visual Representation
counter()
│
├── count = 0
│
└── returns increment()
│
└── remembers count variable
The function keeps access to count even after counter() has ended.
What Does nonlocal Do?
nonlocal allows modifying variables from the enclosing function scope.
Without nonlocal, Python creates a new local variable instead.
Without nonlocal
def counter():
count = 0
def increment():
count += 1
return count
return increment
Error
UnboundLocalError:
local variable 'count' referenced before assignment
Python thinks count is local to increment().
Correct Version With nonlocal
def counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
global vs nonlocal
| global | nonlocal |
|---|---|
| Modifies global variables | Modifies enclosing scope variables |
| Works at file/module level | Works inside nested functions |
| Less safe | Safer for closures |
| Shared everywhere | Private to closure |
Example Using global
count = 0
def increment():
global count
count += 1
This changes a variable shared by the whole program.
Example Using nonlocal
def counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
This creates private persistent state.
Persistent State Without Classes
Closures are often used instead of classes for small stateful systems.
Closure Version
def accumulator(start):
total = start
def add(value):
nonlocal total
total += value
return total
return add
acc = accumulator(100)
print(acc(20))
print(acc(30))
Output
120
150
Equivalent Class Version
class Accumulator:
def __init__(self, start):
self.total = start
def add(self, value):
self.total += value
return self.total
Closures provide a lighter functional alternative.
Real Use Cases for Closures
Closures are commonly used for: - decorators - counters - caching systems - state tracking - callback functions - factories - retry systems
Common Mistakes
Forgetting nonlocal
count += 1
without:
nonlocal count
Returning the Function Call Instead of the Function
Wrong:
return increment()
Correct:
return increment
Key Takeaways
- Closures remember variables from outer scopes
- Python uses lexical scoping
nonlocalmodifies enclosing scope variables- Closures can maintain persistent state
- Closures are powerful alternatives to small classes
- Closures are heavily used in decorators and functional programming