Closures in Python
A closure refers to a situation where one function is defined inside another function, and the inner function references a variable from the outer function (not a global variable). Furthermore, the outer function returns the inner function. Thus, the inner function, together with the external variables it references, forms a closure.
1. Basic Concept of Closures
- The formation of a closure requires three conditions:
- There must be a nested function (a function defined inside another function).
- The nested function must reference a variable defined in the outer function (i.e., a free variable).
- The outer function must return the nested function (rather than calling it).
2. Simple Example of a Closure
Suppose we need a function that can record and return the number of times it has been called:
def counter():
count = 0 # Variable of the outer function
def inner():
nonlocal count # Declare count as nonlocal to allow modification
count += 1
return count
return inner # Return the inner function, not the result of calling it
# Create an instance of the closure
c = counter()
print(c()) # Output: 1
print(c()) # Output: 2
- Here,
counteris the outer function andinneris the nested function. innerreferences the external variablecount, andcounterreturns theinnerfunction.- When calling
c = counter(),cactually points to theinnerfunction but retains the state of thecountvariable, even after thecounterfunction has finished executing.
3. How Closures Work
- In Python, functions are first-class objects and can be passed around and returned like variables.
- When the outer function executes, it creates a local scope containing its local variables (e.g.,
count). - The inner function remembers the scope in which it was defined (i.e., the local scope of the outer function). Even after the outer function ends, the inner function can still access these variables.
- This "memory" is implemented through the
__closure__attribute of the function, which stores all referenced external variables.
4. Inspecting Closure Details
The __closure__ attribute can be used to observe the variables referenced by the closure:
def example(x):
def inner(y):
return x + y
return inner
f = example(10)
print(f.__closure__) # Output: (<cell at 0x...: int object at 0x...>,)
print(f.__closure__[0].cell_contents) # Output: 10 (the value of the external variable x)
__closure__is a tuple containing "cells" for all referenced external variables.- The
cell_contentsattribute of each cell stores the actual value of the variable.
5. Closures and Mutable State
- If a closure needs to modify an external variable, it must use the
nonlocaldeclaration (Python 3 and above):
def accumulator(start=0):
total = start
def add(n):
nonlocal total # Declare total as nonlocal
total += n
return total
return add
acc = accumulator(10)
print(acc(5)) # Output: 15
print(acc(3)) # Output: 18
- Without
nonlocal,total += nwould be treated as creating a new local variable insideadd, leading to an error.
6. Common Use Cases for Closures
- State Preservation: Such as counters, accumulators, avoiding the use of global variables.
- Deferred Calculation: Closures can capture some parameters first and perform calculations later upon subsequent calls.
- Decorators: Decorators themselves are an application of closures; they take a function as an argument and return a new function.
7. Precautions
- Closures may cause memory leaks because they hold references to external variables for a long time.
- Be cautious about variable binding when creating closures in loops:
# Incorrect example: all closures share the same i
functions = []
for i in range(3):
def inner():
return i
functions.append(inner)
print([f() for f in functions]) # Output: [2, 2, 2] (not the expected [0,1,2])
# Correct approach: use default parameters to capture the current value
functions = []
for i in range(3):
def inner(x=i): # Bind the current value of i using a default parameter
return x
functions.append(inner)
print([f() for f in functions]) # Output: [0, 1, 2]
Through the steps above, the core mechanisms and application scenarios of closures are clearly presented. By nesting functions and referencing variables, closures enable state encapsulation and persistence.