A closure is an inner function that remembers the variables from the function that built it, even after that outer function has returned. The remembered names aren't copied into the inner function; they're kept alive by reference, in a small piece of bookkeeping Python attaches to the function object. This lesson covers what a closure is, how to spot the free variables that get captured, how to build per-customer pricing functions with a function factory, how to inspect the captured state with __closure__, how nonlocal lets the inner function update that state, and the late-binding pitfall that catches almost everyone the first time they build closures inside a loop.
A function defined inside another function can use the names of the enclosing function. That part is normal scoping. The interesting part is that the inner function keeps working even after the outer function has finished and returned, and it still sees those enclosing names. That's a closure: an inner function plus the environment it captured from its birthplace.
make_greeter runs, builds a greet function, and returns it. By the time greet_alice() is called on the next line, the call to make_greeter("Alice") is long over. The customer_name parameter should be gone with it. But it isn't. The returned function still knows customer_name == "Alice", because the function object carries a reference to that variable's cell with it.
The same factory called with "Bob" produces a different function with a different captured customer_name. Two greeters, two captured strings, no interference. Each closure has its own private environment.
The shape that makes this a closure (and not just a regular nested function) is three things together:
If any of those is missing, you have a nested function but not really a closure in the useful sense. The captured environment only matters if the inner function survives long enough to be called after its creator returns.
The cyan box is the outer call; it disappears once it returns. The orange box is the cell, a small container Python uses to hold a name that's referenced from a nested scope. The green box is the inner function that gets returned. The cell stays alive because the returned function keeps a reference to it, which is why greet_alice() still sees "Alice" long after make_greeter has finished.
Inside the inner function, the name customer_name is a free variable: a name that's used in the function but not defined by it and not a parameter of it. Python resolves free variables by looking at the enclosing function's scope (and then the module scope, and then builtins, in the usual LEGB order).
The thing that makes a free variable special, compared to just any unknown name, is that Python knows about it at compile time. When the function is built, Python sees that customer_name is referenced but not assigned and not a parameter, and it wires up a closure cell for it. That's why the inner function can find the value later, even after the outer call has returned.
Inside apply, price is a parameter (bound when apply is called), and percent is a free variable (captured from make_discount's scope). Each call to make_discount produces a new apply function with its own captured percent. ten_off remembers 10; twenty_off remembers 20. Calling either one only needs price.
A free variable isn't the same as a global. The free variable is captured from an enclosing function's local scope, not from the module. If you replace the outer function's local with a module-level name, the inner function would read the module-level name instead, and you'd lose the per-call isolation.
This version isn't a closure. There's only one percent, and it lives at the module level. Both calls read whatever the current module-level value happens to be. Closures matter because the captured value is private to that specific inner function and survives across other code changing module state.
Cost: A closure cell is small (about the size of one reference) per captured name. The cost is the same whether you capture one name or several. If you find yourself capturing five or more names, a small class with attributes is usually clearer to read, not because of performance but because of clarity.
The make_discount example is the classic function factory: a function whose only job is to return a customized version of another function. Function factories are where closures earn their keep, because they let you parameterize behavior once and reuse the resulting function many times without re-passing the same arguments.
A multiplier factory makes the shape obvious:
Three different multipliers built from the same factory, each with its own captured factor. The factory itself is six lines; everything else is reuse.
In an E-Commerce setting, the same shape gives you per-discount-tier pricing functions you can hand around to whichever piece of code needs them:
Three pricing functions, each carrying its own discount percent. The caller never has to remember which percent goes with which tier; the function knows.
The same shape works when the captured value is structured. Suppose each customer has a personal discount profile (a base percent plus a loyalty bonus). A factory takes the profile and returns a price function tied to that one customer:
Alice gets 15% off (10 base + 5 loyalty), Bob gets nothing, Carol gets 30% off. Each price function captures total from the call to build_customer_pricer and uses it forever after. The factory is also doing a little setup (computing total once) that the inner function gets for free on every call.
Function factories are the underlying idea behind decorators, but you don't need decorators to use them. Any time you find yourself writing def f(x): return some_operation(x, fixed_arg) for several different fixed_args, a factory will save you the repetition.
Python doesn't hide the closure machinery; it puts it right on the function object. Every function has a __closure__ attribute, and every function has a __code__ attribute that knows which names are free.
For a function with no closure, __closure__ is None:
No captured names, no closure cells. The co_freevars tuple is empty.
For a function returned from a factory, __closure__ is a tuple of cell objects, one per free variable. Each cell holds the captured value, and you read it with .cell_contents.
The co_freevars tuple lists the names that were captured (just percent here). The __closure__ tuple has one cell, and its cell_contents is the captured value, 10. The exact memory address in the cell repr varies between runs; the shape is what matters.
When a closure captures more than one name, the order of co_freevars matches the order of __closure__:
Notice that base_percent and loyalty_bonus are not captured. The inner function never references them; it only references total. The compiler only allocates closure cells for the names that are actually used, which is one reason captures stay cheap.
Inspecting __closure__ is mostly a debugging and learning tool. In day-to-day code you write closures without thinking about the cells. But when something behaves unexpectedly (a closure that keeps returning the same value, for example), a quick look at __closure__ will often tell you what the function is actually carrying.
Cost: Reading __closure__ and cell_contents is constant time. There's no special cost. The main use is debugging, not hot-path inspection.
nonlocalThe closures shown so far all read their captured names. Reading is enough for most function factories. But sometimes you want the inner function to update the captured value across calls, the way a counter or a running total would.
By default, an assignment inside the inner function creates a new local variable; it doesn't touch the captured one. nonlocal is the keyword that tells Python "this name belongs to the nearest enclosing function, not to me, so writes should go there".
Here, we just need the one piece: nonlocal name lets the inner function rebind a name from the enclosing function.
A counter is the simplest demonstration:
Each call to ticker() reads the current count, adds one, rebinds the same name in the enclosing scope, and returns the new value. The state lives in the closure cell, not in the inner function. Three calls, three different return values, because the cell keeps updating in place.
Remove the nonlocal and the same code breaks:
Without nonlocal, the count += 1 makes Python treat count as a local in next_value. The local has no initial value when the line runs, so reading it raises UnboundLocalError. The fix is to add nonlocal count at the top of the inner function.
An invoice-number factory is the same shape with a less abstract use:
Every call to invoice() hands back the next number and advances the captured state. Two invoice counters built from the same factory don't share state; each has its own next_id cell.
invoice_a and invoice_b are independent. Counter a advances 1, 2, 3, then 4. Counter b advances 5000, 5001, 5002. The two cells are unrelated; the factory creates a fresh one per call.
A running total is the same shape with addition instead of incrementing:
The captured total accumulates across calls. Whatever the caller passes in gets added; the new running total is what comes back.
There's a small but useful escape hatch: if the captured value is a mutable container (a list, dict, or set), you can mutate it through the closure without nonlocal, because mutating isn't rebinding.
No nonlocal needed. The events name still points at the same list object across calls; we're calling .append on that list, not rebinding events. This is a common pattern, but it's worth being aware that it only works because the value is mutable. The moment you write events = events + [event], you'd be rebinding, and you'd need nonlocal again (or the code would silently create a fresh local).
Here's the bug that almost everyone hits the first time they build closures in a loop. The closure remembers the variable, not the value at the time the inner function was defined. When all the closures end up sharing the same variable, they all end up returning the same final value.
The canonical demonstration uses lambda, but the bug is about closures, not about lambdas; a def version has the same problem. Here's both, side by side.
You'd expect [0, 1, 2]. You get [2, 2, 2]. Three separate closures, all built inside the same loop, all returning the same value.
The reason is that i is one variable, shared across every iteration of the loop. The loop rebinds i to a new value each pass, but it doesn't create a new variable. Every inner function captured a reference to that same i. By the time you call any of the saved functions, the loop has finished, and i is sitting at its final value, 2. All three closures read the same cell and see the same 2.
The red cell is the shared i. All three closures point at it. When the loop ends, i is 2, so every closure that reads i sees 2.
This is called late binding because the value of the free variable is resolved late, at call time, not at the time the inner function was defined. Most of the time, late binding is what you want (a closure should reflect the current state of the cell it captured). Inside a loop where the captured name is the loop variable, it's almost never what you want.
The most common fix is to turn the loop variable into a default argument of the inner function. Default arguments are evaluated once, at the time the inner function is defined, so each iteration produces a function with its own captured value.
The i=i looks weird but it's a normal default argument. The left i is the parameter name of the lambda; the right i is the current value of the loop variable, evaluated when the lambda is built. By the time the loop ends and you call funcs[0](), that lambda's i parameter still defaults to 0, because that was the value of the outer i when the lambda was defined.
The def version of the same fix:
Same trick: i=i makes i a parameter of grab, with a default that's the loop variable's current value. The inner function no longer has i as a free variable; it's a parameter now, so it doesn't go through the closure cell at all.
The other fix is to introduce an extra function whose job is to take the loop variable as an argument and return a closure over that argument, not over the loop variable directly. Because function parameters are local, each call to the helper produces a separate binding.
Each call to make_grabber(i) evaluates i once (passing its current value as the argument value), then returns a closure that captures value. Three calls, three separate value parameters, three separate closures. The shared loop variable never gets captured by any inner function.
Of the two fixes, the default-argument trick is shorter for a one-liner. The factory approach is clearer for more complex inner functions, because it doesn't mix the closure variable with the parameter list of the inner function. Either one solves the bug; both are common in real code.
This is the factory pattern from earlier, used inside a loop. It doesn't trip the late-binding bug because make_discount takes its argument as a parameter (percent), so each call gets its own fresh binding. The pitfall only shows up when the inner function captures the loop variable directly.
Cost: All three patterns (broken, default-argument fix, helper factory) have the same runtime cost per call. The broken version isn't faster; it's just wrong. Pick the fix based on readability for the situation.
A closure carries state. A class carries state too. Once you've seen both, the question "which one should I use?" comes up naturally, because either can do the job.
A class version of the counter factory:
A closure version of the same thing:
Both work, both keep state, both isolate state per instance. Which one fits depends on what else you need:
| Need | Closure | Class |
|---|---|---|
| One operation, simple state | Cleaner | Fine |
| Several related operations on shared state | Awkward (one function returns) | Cleaner (methods) |
| Easy serialization (pickle, JSON) | Harder | Easier |
| Inspect or change state from outside | Harder (need to expose getters) | Direct attribute access |
| Inheritance, subtyping | Not really | Yes |
| Function-shaped API (callbacks, handlers) | Natural | Have to define __call__ |
| Type hints for the produced thing | Callable[..., T] | The class itself |
A rule of thumb that holds up well: if the state has one operation, a closure is usually the cleaner choice. The factory function describes the setup, and the returned function describes the operation. The reader doesn't have to learn a class to understand what's going on.
If the state has multiple operations (an invoice counter that can both issue a new ID and let you reset it and query the current value), a class is usually clearer. You'd have to return a tuple of functions, or a dict of functions, to do the same thing with closures, and at that point you've reinvented a worse version of a class.
Three operations on the same state. A closure version would either return three functions (returned together as a tuple) or pack them into a dict, both of which are clunkier than just having three methods on a class.
The other thing closures do naturally is fit into APIs that want a function: a sort key, a callback, a key extractor, a filter predicate. You hand the closure to the API, and the API calls it the way it would call any function. A class instance can also be used in those spots if it defines __call__, but that's more ceremony than most cases need.
make_price_filter(15.00) returns a closure that captures the budget. filter calls it once per product. The whole filter rule fits in a six-line factory, and the caller passes it to filter like any function. A class version would work but would add three lines for __init__ and __call__ without changing what the code does.
10 quizzes