Closures & late binding

*args & **kwargs was about binding a call to parameters. This page is about a different binding: a nested function that reads a name from an enclosing function binds to that name's storage cell, not to whatever value happened to be in it when the nested function was written. That single fact explains the single most-reported Python surprise for anyone coming from Java's captured-effectively-final lambdas — three lambdas built in a loop that all print the same number — and the two idiomatic ways to avoid it.

closureslate binding nonlocal__closure__

A frame, a cell, and who's pointing at it

When a nested function refers to a name from the function that encloses it, CPython does not copy the value in. It creates a cell — a small box that outlives the enclosing call — and both the enclosing frame's variable and the nested function's __closure__ point at that same cell. Reading the name, from either side, always reads whatever is in the cell right now. Pick an experiment and watch which boxes end up wired to the same cell, and which don't.

Late binding is a lookup-time rule, not a definition-time one

A lambda (or any nested def) that mentions a free variable does not "remember" that variable's value from the line where the lambda was written. It remembers where to look — the cell — and looks there fresh every time it is called. If the loop that created three such lambdas has already finished by the time any of them run, every one of them looks at the same cell and finds the same final value. This is not a bug and not specific to lambda: an ordinary nested def inside a loop has the exact same behaviour, lambda just makes it fit on one line and therefore shows up in code review far more often.

The fix, and the tool that generalises it

The idiomatic fix is the lambda i=i: i pattern above: a parameter's default value is evaluated exactly once, at def time, and stored directly on the function object — no cell, no later lookup, no shared state to be surprised by. It looks like it's rebinding i to itself, but what it is really doing is smuggling the current value in through a different door: parameter defaults.

When the value you want to freeze in is a whole set of arguments rather than one loop variable, functools.partial(fn, *args, **kwargs) is the same idea, spelled for the general case: it builds a new callable with some arguments already baked in, evaluated once, right when you call partial — not a closure over anything that might still change.

Takeaways: a nested function closes over a cell, not a value — reading a free variable always reads the cell's current contents, which is why three lambdas from one loop can all print the same last value. nonlocal lets an inner function write through to that cell instead of shadowing it with a new local. The fix for the loop trap is a default argument (i=i), evaluated once at def time; functools.partial is the same freeze-it-now idea for a whole call. fn.__closure__ is the tuple of cells a function actually closed over, and cell.cell_contents reads what's inside one right now.