# Closures

Source: https://codewiki.com/python/closures/

> - **what**: A closure is a function associated with enclosing lexical bindings; it can read or update them even after the call that defined it has returned.
> - **trap**: A closure retains variable bindings, not values frozen when the function is created, so callbacks built in a loop may all read the final iteration value.
> - **fix**: Use `nonlocal` to rebind enclosing state; when each callback needs an independent value or state, bind an argument at creation or call a factory per iteration.

## What it is and why it exists

A closure is a function object together with access to the enclosing bindings it needs from its definition site. In Python, the usual shape is an outer function that creates and returns an inner function. The outer call can return while the inner function keeps using bindings created by that particular call.

A name used by an inner function but not bound in its own code block is a free variable. The lexical scope that supplies the binding is an enclosing scope. The function's code and access to those bindings provide the complete context needed when it runs.

Closures let lexical scope keep working when a function is passed around as a value. They suit callbacks that carry a small amount of configuration or a narrow function interface over state, without a module global. You encounter them in function factories, decorators, validators, event handlers, and key functions passed to `sorted()`.

Returning the inner function isn't a semantic requirement for a closure; it merely makes the closure's lifetime easy to observe. A nested function that refers to an enclosing function binding is still a closure when called before the outer call returns. Conversely, a nested function that uses only parameters, locals, and globals has `None` in `__closure__`.

A closure fits one main operation and a small amount of plainly named state. If callers need several public operations, validation rules, a serializable representation, or inheritance, a class usually states the contract more directly. Choose from the interface and ownership model, not a blanket claim about speed or memory.

## How it works

Python resolves names from source-code nesting, not from whichever function called the current one. When the compiler finds inner code that refers to a name bound in an outer function, it classifies that name as free in the inner code and arranges shared storage. A same-named local elsewhere on the call stack doesn't affect that choice.

Each execution of the outer function creates bindings for that call. Creating the inner function gives the function object a route to the bindings it needs; returning the function doesn't copy their current objects into a snapshot. A later call reads the objects those bindings refer to at that time, so rebinding changes what the closure subsequently sees.

You can follow closure creation and use in this order:

1. Call the outer function and create local bindings for that invocation.
2. Execute the inner function definition and connect the enclosing bindings it actually uses.
3. Pass the inner function out of the outer call, or use it within that call.
4. After the outer call ends, bindings still referenced by the closure remain alive.
5. Call the closure and read or update those same bindings.

A closure doesn't automatically retain the outer function's entire local namespace. Only bindings that the inner code actually uses as free variables need to remain. One such binding can still refer to a large mutable object or object graph, so "only one captured name" says little about retained data size.

### Reading, mutation, and rebinding

Reading a free variable needs no declaration. Mutating the mutable object referenced by a free variable also needs no declaration because the binding itself doesn't change. `nonlocal` is needed when the inner function makes the name refer to another object.

| Inner operation | What happens | Needs `nonlocal` |
| --- | --- | --- |
| `return count` | Reads an enclosing binding | No |
| `items.append(value)` | Mutates the bound list | No |
| `count += 1` | Reads and then rebinds the name | Yes |
| `items = []` | Rebinds the name to a new list | Yes |

`count += 1` is easy to misread. Integers can't be mutated in place, so the statement reads the old integer, computes a new one, and assigns it to `count`. Any assignment in the function body makes `count` local there by default; without `nonlocal count`, reading that not-yet-assigned local raises `UnboundLocalError`.

### The target of `nonlocal`

`nonlocal name` targets the nearest enclosing function scope where the name is already bound. It doesn't create an outer binding and can't target the module-global scope. If the compiler finds no suitable binding, it raises `SyntaxError` before the code runs.

`global` and `nonlocal` solve different problems. `global` directs assignment to the module namespace. Mechanically replacing `nonlocal` with `global` in generated code turns state owned by one factory invocation into state shared across the module.

Several functions created by the same outer invocation can share a binding; functions created by separate invocations usually get separate bindings. "Built by the same factory" therefore doesn't mean shared state. The relevant questions are which invocation created each function and which binding it uses.

## Examples

These four examples begin with read-only configuration, then add mutable state, shared operations, and loop-created callbacks. Every shown output comes from running the corresponding file with local `python3`.

### Carrying read-only configuration

The label factory configures a prefix once and returns a function that takes only an order number. It only reads `prefix`, so it doesn't need `nonlocal`.

<!-- quick -->

```python
# file: label_factory.py
def make_labeler(prefix):
    def label(order_id):
        return f"{prefix}-{order_id:04d}"

    return label


invoice_label = make_labeler("INV")
return_label = make_labeler("RET")

print(invoice_label(7))
print(return_label(7))
print(invoice_label.__code__.co_freevars)
print(invoice_label.__closure__[0].cell_contents)
```

```text
INV-0007
RET-0007
('prefix',)
INV
```


<!-- /quick -->

The two `make_labeler()` calls create separate `prefix` bindings, so the label functions don't affect each other. `co_freevars` names the free variables, and cells at the matching positions in `__closure__` hold their bindings. Positional inspection is useful for diagnosis, not as an application interface.

The string object happens to be immutable, but the closure hasn't copied it. The function still reaches `"INV"` through its own binding. If another closure later rebinds that same name, a reader sharing the binding sees the new object.

### Keeping state with `nonlocal`

The counter must make `count` refer to a new integer, so `record()` declares `nonlocal count`. Each factory invocation produces an independent count.

```python
# file: attempt_counter.py
def make_attempt_counter(start=0):
    count = start

    def record():
        nonlocal count
        count += 1
        return count

    return record


email_attempt = make_attempt_counter()
sms_attempt = make_attempt_counter(10)

print(f"email: {email_attempt()}, {email_attempt()}")
print(f"sms: {sms_attempt()}")
print(f"email: {email_attempt()}")
```

```text
email: 1, 2
sms: 11
email: 3
```

Interleaving the calls shows that the counters don't leak state. Assigning `email_attempt` to another name wouldn't create a new counter because both names would still refer to one function object. Call the factory again when you need independent state.

This interface lets a caller advance the count but not directly set `count` to an arbitrary value. That is encapsulation by scope, not a security boundary. Code that inspects the function object can still see the cell contents.

### Sharing one cell between operations

One `make_quota()` call returns two functions. `reserve()` rebinds `remaining`, while `available()` reads it; both functions connect to the same cell.

```python
# file: shared_quota.py
def make_quota(limit):
    remaining = limit

    def reserve(units):
        nonlocal remaining
        if units <= 0:
            raise ValueError("units must be positive")
        if units > remaining:
            return False
        remaining -= units
        return True

    def available():
        return remaining

    return reserve, available


reserve, available = make_quota(5)
print(reserve(2), available())
print(reserve(4), available())
print(reserve(3), available())
```

```text
True 3
False 3
True 0
```

The failed second reservation leaves `remaining` unchanged, and `available()` observes that shared state. If more operations accumulate, a positional group of anonymous functions becomes hard to read. A class with `reserve()` and `available()` methods is usually clearer at that point.

A shared cell doesn't provide synchronization. If threads or async tasks can interleave the check and deduction, `if units > remaining` followed by `remaining -= units` isn't one business-atomic operation. Add synchronization suited to the execution model or give the state one owner.

### Comparing late binding with creation-time binding

Both lists in the loop create three lambdas. The first group refers directly to the loop variable; the second stores the current object as a default argument.

```python
# file: loop_handlers.py
def make_handlers(queue_names):
    late_handlers = []
    bound_handlers = []

    for queue_name in queue_names:
        # These functions share the loop variable's cell.
        late_handlers.append(lambda: queue_name)
        # The default saves the current object when the function is created.
        bound_handlers.append(lambda queue_name=queue_name: queue_name)

    return late_handlers, bound_handlers


late, bound = make_handlers(["fast", "bulk", "slow"])

print([handler() for handler in late])
print([handler() for handler in bound])
print(late[0].__closure__[0] is late[1].__closure__[0])
print(bound[0].__closure__)
```

```text
['slow', 'slow', 'slow']
['fast', 'bulk', 'slow']
True
None
```

The first group doesn't read `queue_name` until after the loop, when the shared cell points to `"slow"`. This is the late-binding trap. A list comprehension has its own scope, but closures created within one comprehension and called later still share that comprehension variable.

On the second line, the left `queue_name` is the lambda's local parameter and the right name is evaluated when the lambda definition runs. The saved object lives in the function defaults rather than a closure cell, so `__closure__` is `None`. When callers shouldn't be able to override that parameter, a one-call helper factory is often clearer.

## Pitfalls

### Describing a closure as a value snapshot

> **Pitfall:** "A closure saves the variable's value at that moment" predicts the wrong result for rebinding and loop-created callbacks. A closure normally retains access to a binding rather than freezing an object when the function is created.

**Fix:** map each free name to its binding and mark when function creation and invocation occur. If you need a snapshot, express that decision with a default argument, a helper factory, or an explicit copy, and state whether the copy is shallow or deep.

### Omitting or misusing `nonlocal`

> **Pitfall:** Assignment to a name inside the inner function makes that name local throughout the function's code block by default. Even an assignment in a branch that never runs affects compile-time classification.

**Fix:** put `nonlocal` near the start of the inner function when it truly rebinds enclosing function state. Don't add it mechanically for reads or in-place mutation, and use `global` only for module-owned state. For `UnboundLocalError`, inspect every assignment in the function, not just the reported line.

### Placing the factory call at the wrong lifetime

> **Pitfall:** Calling a stateful factory once outside a loop and registering the same result for several consumers makes them share cells unexpectedly. Calling the factory again for every event has the opposite bug: persistent state keeps resetting.

**Fix:** decide whether state belongs to the application, queue, request, or individual callback, then place the factory call at that lifetime boundary. Interleave calls to two instances that should be independent and repeatedly call one instance that should preserve state.

### Hiding late binding inside a loop

> **Pitfall:** Late binding isn't limited to `lambda`. A nested `def`, callback registration, task completion handler, or comprehension can make several functions share the final iteration binding.

**Fix:** call all generated functions after the loop and assert each result. A default argument works when you only need to fix one value. When each callback also needs independent mutable state, call a named factory once per iteration.

### Retaining an object for too long

> **Pitfall:** A long-lived registered closure keeps the bindings it uses reachable. If a binding points to a request context, cache, service container, or large dataset, those objects may outlive the actual work.

**Fix:** extract the small value the callback needs instead of capturing a broad context for one field. Unregister the callback when its owner finishes. Files, sockets, and transactions need explicit context management rather than waiting for a closure to be collected.

### Depending on `__closure__` positions

> **Pitfall:** `function.__closure__[0]` has no stable business meaning. Adding another free variable can change the name-to-cell positions, and a function with no free variables has `None` instead of a tuple.

**Fix:** for diagnosis, pair `function.__code__.co_freevars` with `function.__closure__` by position or use `inspect.getclosurevars()` to classify bindings. Production code should read and change state through the public function interface, not treat cell layout as a protocol.

<!-- deep -->

## Bindings, function objects, and cells

At the language level, the important guarantees are lexical name resolution and continued access to enclosing bindings. CPython implements that shared storage with a cell object. Other Python implementations must preserve observable semantics, but they needn't use exactly the same bytecode or object layout.

### Compile-time name classification

Before a function runs, the compiler classifies names as local, free, cell, or global. An outer function local that inner code references appears in the outer code object's `co_cellvars`. For the inner code object, the same name appears in `co_freevars`.

Classification applies to the whole code block rather than changing along execution paths. Any assignment inside an inner function makes the target local by default unless a `global` or `nonlocal` declaration changes the classification. Moving the assignment into `if False:` therefore doesn't make an earlier read fall back to the outer binding.

| Observation point | Attribute | What it represents |
| --- | --- | --- |
| Outer code object | `co_cellvars` | Outer locals also referenced by inner code |
| Inner code object | `co_freevars` | Names that enclosing scopes must supply |
| Inner function object | `__closure__` | Cells corresponding by position to `co_freevars` |
| Diagnostic helper | `inspect.getclosurevars()` | Resolved nonlocals, globals, builtins, and unbound names |

A code object describes compiled behavior and may be shared by function objects from several factory calls. `__closure__` belongs to a function object and connects it to cells created by a particular execution. Don't mistake a shared code object for shared runtime state.

### Cell identity and sharing

A cell is a small container for the current object reference. When `nonlocal` rebinds a captured name, the reference inside the cell changes, and every other closure sharing that cell sees the new object on its next read. When code calls `append()` on a captured list, the cell still refers to the same list, but the list's contents change.

One outer invocation can create several functions that share a cell, which is why `reserve()` and `available()` see one quota. Calling the factory again creates a new outer binding and a new cell, isolating the new quota from the old one. Comparing cell identity can test this relationship, but it shouldn't drive application behavior.

`__closure__` is either `None` or a tuple of cells. Reading `cell_contents` can help locate leaked state. Directly changing cell contents couples application code to internals and bypasses validation and invariants that the factory's interface was meant to enforce.

### Default arguments aren't captured cells

A default expression is evaluated when its `def` or lambda expression executes, and the result is stored in the function's defaults. In `lambda item=item: item`, the left `item` is a local parameter, so the body doesn't need to read that name from an outer scope. The idiom solves a timing problem; it doesn't alter Python's closure rules.

A default also stores an object reference rather than automatically copying a mutable object. If a loop variable points to a dictionary that is later mutated, each default may hold the dictionary selected in its iteration while calls still observe that dictionary's newer contents. Make an explicit copy before binding when the contract requires a content snapshot, and choose the required copy depth.

`functools.partial()` can bind arguments to an existing callable in advance. It returns a partial object, not a Python closure that stores those arguments as free variables. Choose among a default argument, helper factory, and `partial()` according to the desired signature and readability instead of describing them as one mechanism.

### Multiple nesting levels and class scope

With several nested functions, `nonlocal` selects the nearest enclosing function binding with the same name. It can't skip that binding to name a farther one, and there is no syntax for selecting a nesting level. If several levels carry same-named state, renaming is often clearer than relying on implicit depth.

An ordinary class namespace isn't an enclosing function scope for method bodies. If a class body assigns `rate = 0.2`, a method's bare `rate` doesn't capture it like a nested function would. Access the class attribute through `self.rate`, `type(self).rate`, or the class name. Calling both class and function bodies "outer scopes" hides this difference.

A function defined inside a method can capture that method's local names, including `self`. As a result, a returned callback may keep the whole instance reachable. When it only needs one immutable instance field, first extract that field into a meaningful local name and then decide whether to capture it.

### Diagnosing shared state

When closure state looks wrong, start with source and call lifetimes, then inspect runtime objects. These checks usually locate the problem:

1. Use `co_freevars` to list the free names the inner function actually reads.
2. Pair those names by position with cells in `__closure__`.
3. Check whether two functions reference the same cell, not only whether their `cell_contents` compare equal.
4. Use `inspect.getclosurevars()` to separate nonlocal, global, builtin, and unbound names.

Equal contents don't prove shared state. Two independent counters can both start at integer `0` while owning different cells; a shared cell can point to different objects at different times. Ownership depends on cell identity and the factory invocation boundary.

### Lifetime and execution model

A closure keeps a captured binding alive at least until the last function referring to it becomes unreachable, but exact collection timing still depends on the Python implementation and reference graph. Don't make correctness depend on finalization timing. Resources that must close promptly need explicit closing, a context manager, or a defined unregister operation.

Closure state has no special concurrency guarantee. Threads, tasks, signal handlers, or reentrant callbacks that reach the same closure can still interleave a check followed by an update. The remedies are the same as for other shared mutable state: narrow ownership, serialize access, or use synchronization designed for the execution model.

Standard `pickle` locates ordinary functions by a qualified name in a module, and nested local functions usually can't be imported that way. When work must go to a process pool or cross a process boundary, don't assume a closure is naturally serializable. A top-level function with explicit data arguments is usually more reliable.

### Metadata and wrapper behavior

A closure returned by a decorator is a new function object. Without `functools.wraps()`, its `__name__`, documentation, annotations, and `__wrapped__` link describe the wrapper rather than the wrapped function. That can confuse tracebacks, API documentation, and tools that inspect signatures.

`wraps()` copies selected metadata and exposes the wrapped callable for introspection; it doesn't change which values the wrapper closes over. It also doesn't isolate state, add synchronization, or make a local wrapper serializable. Those remain separate design decisions.

Type annotations can describe the returned callable's signature, but ordinary Python execution doesn't enforce them. For a decorator that preserves an arbitrary callable signature, static typing may need `ParamSpec`; the runtime closure mechanics are unchanged.

### Testing the lifetime contract

Most closure tests should use the public callable rather than `__closure__`. Exercise a sequence of calls and assert the returned values or visible side effects. That keeps the test valid if the implementation later changes from a closure to a class.

A late-binding test must defer invocation until after the loop or registration phase. An immediate call inside the loop observes the current value and can make broken code look correct. Use distinct iteration values so a duplicated final value can't hide.

State-isolation tests need at least two factory results. Interleave their calls, then verify that each sequence advances independently. For a deliberately shared pair of operations, test both views after every mutation.

### Closure or class

A closure is convenient when the interface has one main call, little state, and no need for public inspection. It also lets a configured function go directly to an API that expects a callable. Well-named factories and returned functions should reveal who owns the state and how many times the factory must be called.

A class fits multiple operations, explicit attributes, protocol implementations, serialization, and state-transition tests. Packing five or six closures into a dictionary or positional tuple usually means you're manually imitating an object interface. Switching to a class then makes the structure match the public contract.

<!-- /deep -->

[Checkpoint: python/closures](https://codewiki.com/python/closures/#checkpoint)

## Further reading

- [Python language reference: naming and binding](https://docs.python.org/3.14/reference/executionmodel.html#naming-and-binding)
- [Python language reference: the `nonlocal` statement](https://docs.python.org/3.14/reference/simple_stmts.html#the-nonlocal-statement)
- [Python data model: user-defined functions](https://docs.python.org/3.14/reference/datamodel.html#user-defined-functions)
- [Python `inspect.getclosurevars()`](https://docs.python.org/3.14/library/inspect.html#inspect.getclosurevars)
- [Python programming FAQ: lambdas in loops](https://docs.python.org/3.14/faq/programming.html#why-do-lambdas-defined-in-a-loop-with-different-values-all-return-the-same-result)
- [PEP 3104: Access to Names in Outer Scopes](https://peps.python.org/pep-3104/)
