runtime
74 / 250
lesson 74 Advanced Functions & Recursion · intermediate

Functions that remember: closures

A function defined inside another function remembers the outer function's variables, even after the outer function has returned. That lets you configure a function once, or give it private state.

You'll be able to
  • Write a factory: an outer function that defines and returns an inner function
  • Explain why each returned function keeps its own copy of the outer variables
  • Update remembered state with nonlocal, and recognise the UnboundLocalError when it is missing
The idea

Functions are values: you have passed them as keys. A function can also return a function. An inner def can use the outer function's parameters, and Python keeps those variables alive for as long as the inner function exists. That combination of a function and the variables it remembers is a closure.

The use: configure once, call many times. A shop has a member discount and a clearance discount. Instead of passing the rate to every call, build one function per discount.

Run each example to see what it prints. Running examples doesn't count toward progress.

example-1.py · One function per discount
def make_discount(rate):
    def apply(price):
        return price * (1 - rate)
    return apply

member = make_discount(0.10)
clearance = make_discount(0.50)
print(member(40), clearance(40))
print(member(25.0))

A closure can also keep state that changes between calls, such as a counter. Reading an outer variable works directly, but assigning to it (count += 1) would make Python treat count as a new local variable of the inner function. nonlocal count says: this name is the outer function's variable; update that one.

example-2.py · Invoice numbers that never repeat
def make_numberer(prefix):
    count = 0
    def next_number():
        nonlocal count
        count += 1
        return f"{prefix}-{count:04d}"
    return next_number

invoice_no = make_numberer("INV")
refund_no = make_numberer("RF")
print(invoice_no(), invoice_no(), refund_no(), invoice_no())
example-3.py · Forgetting nonlocal
def make_counter():
    count = 0
    def increment():
        count += 1
        return count
    return increment

clicks = make_counter()
print(clicks())

When to use it: a configured helper (a discount, a formatter, a validator with its limits) or a small private state (an ID generator, a rate limiter) without needing a class. Mistakes to avoid: forgetting nonlocal when updating; returning apply() (the result of calling it) instead of apply (the function); and expecting two closures from the same factory to share state: each call to the factory makes fresh variables.

Review
  • An inner function can use the outer function's variables; returned, it keeps them: a closure.
  • Each call to the factory creates fresh variables, so each returned function has its own.
  • Assigning to a remembered variable needs nonlocal, or Python raises UnboundLocalError.
  • Next: a function that calls itself, to walk data nested to any depth.
Practise this

Loading your progress…

To master this lesson, solve both challenges without hints or the answer, and pass the concept check.

Functions that remember: closures — Advanced Functions & Recursion · Runtime