FunctionsChapter 58 of 114
Closures
A function that remembers the variables it was built with.
A function built by a function
The inner function keeps access to the outer function's variables, even after the outer one has returned:
def multiplier(factor):
def multiply(n):
return n * factor
return multiply
triple = multiplier(3)
double = multiplier(2)
print(triple(5))
print(double(5))Output
15 10
multiplier finished long before triple(5) ran, and factor is still there. That captured variable is what makes it a closure.
Each call builds a separate one, which is why triple and double do not interfere.
Seeing the captured value
def multiplier(factor):
def multiply(n):
return n * factor
return multiply
triple = multiplier(3)
print(triple.__closure__[0].cell_contents)Output
3
Changing what you captured
Assigning inside the inner function would make a new local. nonlocal says you mean the outer one:
def make_counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
counter = make_counter()
print(counter(), counter(), counter())
fresh = make_counter()
print(fresh())Output
1 2 3 1
The second counter starts again from zero, because it captured its own count.
The late-binding trap
A closure captures the variable, not its value at the time. In a loop, every function ends up seeing the final value:
funcs = []
for i in range(3):
funcs.append(lambda: i)
print([f() for f in funcs])Output
[2, 2, 2]
All three look at the same i, which is 2 by the time they run. The fix is to capture the value with a default argument, which is evaluated immediately:
funcs = []
for i in range(3):
funcs.append(lambda i=i: i)
print([f() for f in funcs])Output
[0, 1, 2]
A factory function does the same thing more readably:
def make(i):
return lambda: i
funcs = [make(i) for i in range(3)]
print([f() for f in funcs])Output
[0, 1, 2]
Where you meet them
Every decorator is a closure — the wrapper captures the function it wraps:
def prefix(word):
def decorate(fn):
def wrapper(*args):
return word + fn(*args)
return wrapper
return decorate
@prefix(">> ")
def say(text):
return text
print(say("hello"))Output
>> hello
They are also how you build a configured function once and use it many times:
def between(low, high):
def check(value):
return low <= value <= high
return check
adult = between(18, 65)
print([adult(age) for age in [10, 30, 70]])Output
[False, True, False]
Closure or class?
A closure with one captured value and one function is lighter than a class. Once you need several methods or want to inspect the state, a class is clearer:
class Counter:
def __init__(self):
self.count = 0
def increment(self):
self.count += 1
return self.count
c = Counter()
print(c.increment(), c.increment())
print(c.count)Output
1 2 2
The class version lets you read c.count. The closure version deliberately does not, which is sometimes the point.
Test yourself
2 questionsWhat does a closure capture?
Show the answer
The variable, so it sees later changes — That is exactly why every lambda in a loop ends up seeing the final value.
How do you fix [lambda: i for i in range(3)] all returning 2?
Show the answer
Capture the value with a default argument, lambda i=i: i — A default is evaluated at definition time, which is the value you wanted. A factory function reads even better.
Classes and Objects
Bundle data and the code that works on it into one thing.