Python Closures
Functions Are Objects
Functions in Python are objects with a type of function. This means they can be:
- Stored in variables
- Passed as arguments to other functions
- Returned from functions
def foo(x):
print(x)
print(type(foo)) # <class 'function'>
def call_func(func, x):
func(x)
call_func(foo, 5) # 5
Nested Functions
A function defined inside another function is called an inner or nested function. It is local to the enclosing function's scope.
def outer(x): # outer / enclosing function
def inner(y): # inner / nested function
print(x + y)
return inner
Terminology
| Term | Refers to |
|---|---|
| Outer / Enclosing function | The function that contains another function |
| Inner / Nested function | The function defined inside another function |
| Free variable | A variable used inside the inner function but defined in the enclosing function |
Accessing the inner function
The inner function cannot be called by name from outside — only via the return value:
func = outer(5) # returns the inner function with x=5 already set
func(6) # 11 (5 + 6)
func(3) # 8 (5 + 3)
inner(3) # ❌ NameError — not accessible directly
Each call creates an independent function
func1 = outer(5)
func2 = outer(1)
func1(1) # 6
func2(1) # 2 — different x, different behaviour
What is a Closure?
A closure is an inner function that: 1. Accesses a free variable from its enclosing function 2. Can still use that variable after the enclosing function has finished executing
def outer(x):
def inner():
print(x) # x is a free variable — defined in outer, used in inner
return inner
func = outer(5) # outer finishes executing here
func() # 5 — x is still accessible inside the closure
The outer function has "closed" — yet the inner function still has access to what was passed to it. This is why it's called a closure.
Practical Example — Collection (Mutable Free Variable)
Closures work naturally with mutable objects (lists, dicts) because you modify them in place — no reassignment needed:
def collection():
lst = [] # free variable (mutable)
def add_value(value):
lst.append(value) # modifies lst directly
return lst
return add_value
add_value = collection()
print(add_value(1)) # [1]
print(add_value(2)) # [1, 2]
print(add_value(3)) # [1, 2, 3]
Each call adds to the same list — the closure preserves state between calls.
The nonlocal Keyword
With immutable types (int, str, etc.) you cannot reassign a free variable directly — Python treats it as a new local variable:
def counter(start):
count = start
def increment(value):
count += value # ❌ UnboundLocalError — creates a new local 'count'
return count
return increment
Fix: use nonlocal to tell Python the variable belongs to the enclosing scope:
def counter(start):
count = start
def increment(value):
nonlocal count # ✅ now refers to the outer 'count'
count += value
return count
return increment
count = counter(2)
print(count(1)) # 3
print(count(1)) # 4
print(count(1)) # 5
nonlocal rules
- Can only be used inside a nested function
- References the closest enclosing scope that has the named variable (not necessarily the outermost)
def outer():
x = 4
def inner():
x = 3
def inner2():
nonlocal x # references inner's x (closest enclosing), not outer's x
x = 2
inner2()
print("inner:", x) # 2 — inner's x was modified
inner()
print("outer:", x) # 4 — outer's x was NOT modified
Closures vs Classes
Closures can mimic simple class behaviour. Both examples below work equivalently:
Closure version
def collection():
lst = []
def add_value(value):
lst.append(value)
return lst
return add_value
Class version
class Collection:
def __init__(self):
self.lst = []
def add_value(self, value):
self.lst.append(value)
return self.lst
Use a closure when the logic is simple and you want to avoid the overhead of defining a full class.
Key Takeaways & Recap
| Concept | Summary |
|---|---|
| Functions are objects | Can be stored, passed, and returned like any other value |
| Nested function | A function defined inside another function; local to the enclosing scope |
| Free variable | A variable used in the inner function but defined in the enclosing function |
| Closure | An inner function that retains access to free variables after the outer function finishes |
| Mutable free variables | Can be modified directly (.append, etc.) — no special keyword needed |
| Immutable free variables | Require nonlocal to reassign; otherwise Python creates a new local variable |
nonlocal |
Declares a variable as belonging to the closest enclosing scope |
| Use case | Preserving state between calls; a lightweight alternative to simple classes |