Chapters
Python114 chapters

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:

Python
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

Python
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:

Python
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:

Python
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:

Python
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:

Python
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:

Python
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:

Python
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:

Python
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 questions

What 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.

Next chapter

Classes and Objects

Bundle data and the code that works on it into one thing.