Closures
You have already seen a function as a note with a rule that is handed to another function. Now a function will create such functions itself—and give them the data they need to take along.
Picture the outer function packing the nested one for a trip. It puts its own variables into the nested function's backpack, and the nested function leaves with it. The outer function finished its work long ago, but the backpack stays with the nested one.
You need functions that multiply a number by 2, by 3 and by 10. Three separate defs differ by a single number. Instead, you can write a function that creates such a function for a number and returns it:
def make_multiplier(n):
def multiply(x):
return x * n
return multiply
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5), triple(5)) # 10 15
print(make_multiplier(10)(4)) # 40Let's walk through the example line by line:
make_multiplier(2)creates a new functionmultiplyand returns it. It goes intodouble.make_multiplier(3)creates anothermultiply, which goes intotriple.double(5)multiplies5by2, andtriple(5)by3, so it prints10 15.make_multiplier(10)(4)makes two calls in a row: the first returns a function, and the second calls it right away with4.
A nested function
A def inside the body of another function creates a nested function. This line runs on every call of make_multiplier, and each time a new function multiply appears.
The name multiply is local: it does not exist outside, the same as local variables. The line return multiply returns the function itself, without parentheses, so double is a function, and you can call it. A nested function can also be called inside the outer one, like any other, but here it is returned.
A closure
Local variables disappear when a function returns its result. But double(5) is called after make_multiplier(2) has finished, and it still sees n equal to 2. A nested function that uses variables of the outer function keeps them with it—in that very backpack. Such a function is called a closure.
Each call of make_multiplier creates its own variable n and its own closure. So double has its own n equal to 2, triple has one equal to 3, and neither function changes the other.
Interviewers like to ask this: "What is a closure?" The answer has two parts:
- it is a nested function that uses variables of the outer function;
- those variables live as long as the closure does, even after the outer function has returned.
nonlocal
A closure can read a variable of the outer function without any preparation. To assign it a new value, the nested function writes nonlocal and the name. This works the same way as global for a global variable:
def make_total():
total = 0
def add(x):
nonlocal total
total += x
return total
return add
add = make_total()
print(add(5), add(3)) # 5 8
print(make_total()(10)) # 10total = 0 runs once, when make_total is called, not on every call of add. So add accumulates the sum: 5, then 8. A new call of make_total() creates a new total, and the new closure starts from zero.
Here the backpack picture needs a caveat. A real backpack would hold copies, but a closure holds the very variables of the outer call. That is why nonlocal changes exactly them, and the next call sees the new value.
Let's watch it live, on two counters. Press "Next step" and watch the "captured" line: a and b each have their own count, and it lives on after make_counter returns.
1def make_counter():2 count = 03 4 def tick():5 nonlocal count6 count += 17 return count8 return tick9 10 11a = make_counter()12b = make_counter()13print(a(), a(), b())
The program calls make_counter and waits for what it returns.
There is no way to reach count from outside: only tick changes it, when called through a or b.
Common mistake
The nested function is missing nonlocal:
def make_total():
total = 0
def add(x):
total += x # ✗
return total
return add
add = make_total()
print(add(5))UnboundLocalError: cannot access local variable 'total' where it is not associated with a valueBecause of +=, the name total became local in all of add, and this local total has no value. The error comes on the call add(5), not on make_total(). Add nonlocal total as the first line of the body of add.
Key points
- A closure is a nested function that uses variables of the outer function.
- Those variables live on after the outer function returns, as long as the closure lives.
- Each call of the outer function creates new variables, so each counter has its own state.
- To assign a new value to a variable of the outer function, write
nonlocaland its name.
Subscribe to take the course
The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.