Skip to content
Theory · Functions as values · lesson 5 of 10

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:

Python
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))  # 40

Let's walk through the example line by line:

  1. make_multiplier(2) creates a new function multiply and returns it. It goes into double.
  2. make_multiplier(3) creates another multiply, which goes into triple.
  3. double(5) multiplies 5 by 2, and triple(5) by 3, so it prints 10 15.
  4. make_multiplier(10)(4) makes two calls in a row: the first returns a function, and the second calls it right away with 4.

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:

Python
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))  # 10

total = 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.

LiveTwo countersstep 1 of 11 · frames: 1

The program calls make_counter and waits for what it returns.

The "captured" line shows the variables the closures hold

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:

Python
def make_total():
    total = 0

    def add(x):
        total += x  # ✗
        return total
    return add


add = make_total()
print(add(5))
TEXT
UnboundLocalError: cannot access local variable 'total' where it is not associated with a value

Because 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 nonlocal and its name.

Subscribe to take the course

The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.

Sign in another way