Closures

RoadmapsPython

Overview

A closure is a function that remembers variables from the scope where it was created, even after that outer scope has finished running. Think of it as a backpack the inner function carries: when the outer function returns, the inner function keeps the variables it packed. The inner function keeps a persistent reference to its enclosing scope's variables across calls, even after the outer function has returned. def make_greeter(greeting): def greeter(name): return f"{greeting}, {name}!" return greeter say_hello = make_greeter("Hello") print(say_hello("Alice")) Closures capture the variable itself, not the value at creation time. This leads to the late binding closure trap in loops. # Late binding closure trap funcs = [lambda: i for i in range(3)] print([f() for f in funcs]) # Outputs [2, 2, 2] When building decorators, closures can accidentally hide the original function's identity. def log(fn): return lambda *args: fn(*args) @log def add(a, b): pass print(add.__name__) # Outputs '<lambda>', not 'add' This is the lost metadata trap. It is covered fully in decorators.

Closures let you build functions with private, persistent state without a class. They are the mechanism that makes decorators possible. Any time you configure a function by wrapping it, a closure is doing the remembering.

Where used: FastAPI, Pydantic validators, micrograd, functools

Why learn this

Code walkthrough

def make_counter():
    count = 0
    def counter():
        nonlocal count
        count += 1
        return count
    return counter

c = make_counter()
print(c())
print(c())
print(c())

Focus: Watch `count` survive between calls - it lives in the closure, not a global.

Common mistakes

Glossary

scope
The specific area in code where a variable is recognized and can be used, e.g. `def f(): x=1` means `x` is in local scope.
decorator
A special function that wraps another function to modify or enhance its behavior without changing its code, e.g. `@app.get('/')`.

Recall questions

Understanding checks

What will the following code output?

30

The `multiplier` inner function captures the variable `n` from the enclosing scope. When `times3` is called with `10`, it multiplies `10` by the captured value `3`.

Why do you need the `nonlocal` keyword when modifying a captured variable inside a closure?

Without `nonlocal`, assigning a value to a variable inside the inner function creates a new local variable, rather than updating the variable in the enclosing scope.

Python's scoping rules dictate that any assignment makes a variable local to the current function. The `nonlocal` keyword explicitly tells Python to rebind the variable in the nearest enclosing scope.

What will the following code output?

[1, 1]

Closures capture the variable `i`, not its value at the time the function is created. By the time the functions are called, the loop has finished and `i` is `1`.

Practice tasks

Closure counter

The `make_counter` function currently relies on a global variable. Modify it to use a closure by moving the `count` variable inside `make_counter` and using `nonlocal` inside the inner function.

Challenge

Implement a logging decorator

Using a closure, write a `@log_calls` decorator that prints the function name and arguments before calling it, then prints the return value. Preserve the wrapped function's name with `functools.wraps`.

Continue learning

Previous: Defining functions and return values

Previous: LEGB rule: variable scope in Python

Previous: First-class functions: passing and returning functions

Next: Function Decorators

Return to Python Roadmap