How do you write a Python closure that accumulates a running total across calls?
answer
- State lives in the factory's frame
- The inner function must rebind, not shadow
- One keyword makes the rebinding legal
- Each factory call yields fresh state
- A class is the readable alternative
basics
~20 sA factory function holds the state in one of its own local variables, and the inner function it returns declares that name nonlocal before updating it. Each call to the factory produces an independent accumulator with its own private total.
solid answer
~40 sWrite a factory. `def make_accumulator(start=0)` binds `total = start`, defines an inner function that declares `nonlocal total`, updates it and returns it, and then the factory returns that inner function. The `nonlocal` is what makes it work: without it, the inner assignment would create a fresh local on every call and the total would never move. Every invocation of the factory creates a new binding, so two accumulators never share state, and several inner functions returned from the same call do share it -- which is how you add a read-only accessor. Before `nonlocal` existed the same effect was faked by mutating a one-element list, which still works but hides the intent. Once the state must be inspected, reset or serialised, an ordinary object with an attribute is the clearer choice.
code
python · 15 linesdef make_accumulator(start=0, step=1):
total = start
def bump():
nonlocal total
total += step
return total
return bump
tick = make_accumulator()
by_five = make_accumulator(start=100, step=5)
print(tick(), tick(), tick()) # 1 2 3
print(by_five(), by_five()) # 105 110
print(tick()) # 4go deeper
Recall the shape: an outer function holding a variable, an inner function that declares it nonlocal and updates it, and the outer function returning the inner one. Be ready to write those few lines from memory on a whiteboard.
Explain why the declaration is needed -- the inner assignment would otherwise create a local -- and why every call to the factory produces independent state. Know the older mutable-container version and be able to say why it reads worse.
Argue the tradeoff. A closure is right for a call counter or a decorator, but once the state must be inspected, reset or serialised an object is better: closure state is opaque to a debugger and closures cannot be pickled across a process boundary.
Decide where captured mutable state is allowed at all. Closures over mutable state are cheap to write and expensive to audit, so set the expectation that anything a second component must read is promoted to an explicit object rather than left as a captured variable.
### The idiom An accumulator closure is two functions and one variable: ```python def make_accumulator(start=0, step=1): total = start # state, owned by this call of the factory def bump(): nonlocal total # rebind the factory's variable, do not shadow it total += step return total return bump # the caller gets a function, not the variable ``` `make_accumulator()` returns `bump`. Calling `bump()` repeatedly walks `total` upward, and the value survives between calls because it lives in the frame of the `make_accumulator` call that created `bump`, not in `bump`'s own frame. `step` needs no declaration -- `bump` only reads it. The `nonlocal` line is the whole trick. Remove it and `total += step` makes `total` a local of `bump` for the entire body, so the function no longer refers to the factory's variable at all. ### Independence is per factory call, not per function Every call to the factory creates a fresh binding, so two accumulators are genuinely separate: ```python tick = make_accumulator() by_five = make_accumulator(start=100, step=5) tick(); tick() # 1, 2 by_five() # 105 tick() # 3 - unaffected ``` This is the property that makes closures a real alternative to objects rather than a curiosity: the factory call plays the role of `__init__`, and the returned function plays the role of a method. ### Several functions can share one state Return more than one inner function and they close over the *same* enclosing binding, which is how you get an accumulator you can both feed and read: ```python def make_running_mean(): total, count = 0.0, 0 def add(reading): nonlocal total, count total += reading count += 1 def mean(): return total / count if count else 0.0 return add, mean ``` `add` declares both names `nonlocal`; `mean` declares nothing because it only reads. One `nonlocal` statement can list several names, separated by commas. ### The older container idiom, and why `nonlocal` beats it Before Python 3.0 there was no `nonlocal`, so the state was smuggled into a mutable container: ```python def make_counter(): state = [0] def bump(): state[0] += 1 # mutation, not rebinding - so no declaration needed return state[0] return bump ``` This still works today, for the reason covered by the rebind-versus-mutate distinction: `state[0] += 1` calls into the list, it never assigns to `state`. But it is worse on two counts. It hides the intent behind an index that means nothing, and it gives up a compile-time check -- a mistyped key in a dict version compiles fine and fails only when that branch runs, whereas a mistyped `nonlocal` target refuses to import. ### Alternatives worth naming in an interview * **`itertools.count`** covers the pure "give me the next integer" case with no closure at all, and it supports a start and a step. * **`itertools.accumulate`** covers running totals over an existing iterable, which is often what the question really is. * **A class.** An object with an attribute and an `__call__` or an ordinary method does the same job once the state grows past a value or two. It is the right answer whenever the state must be *inspected*, *reset*, *logged*, *serialised* or *subclassed*. ### The real limitations of closure state Closure state is deliberately opaque: the only way to reach `total` is through the functions the factory handed out. That is a feature when you want encapsulation and a liability when you are debugging, because there is no attribute to print and no obvious way to reset the counter without building a new one. Closures are also not picklable, so an accumulator cannot be shipped to another process the way an instance of a module-level class can. And every read or write of the captured variable goes through the returned function, so there is no way to swap the storage without changing the callers. ### Where the idiom genuinely earns its place The strongest everyday use is a decorator that keeps per-decoration state -- a call counter, a one-shot initialiser, a small memo -- because the state is naturally scoped to the single decoration and there is no object for it to live on: ```python import functools def counted(func): calls = 0 @functools.wraps(func) def wrapper(*args, **kwargs): nonlocal calls calls += 1 return func(*args, **kwargs) wrapper.call_count = lambda: calls return wrapper ``` That is the shape to reach for when the state belongs to one wrapping and nobody else needs to see it. The moment a second component wants to read the number, promote it to an object.
- Why does the same idiom work with a one-element list and no declaration?Because `state[0] += 1` mutates the list the name already refers to and never assigns to `state` itself, so the compiler still treats `state` as a variable read from the enclosing scope. It works, but it hides the intent behind a meaningless index and gives up a compile-time check that `nonlocal` would have given you.
- Can two functions returned by one factory call share the same accumulator?Yes. Define `bump` and `read` inside the same factory call and return both. They close over the same enclosing binding, so `bump` declaring `nonlocal total` updates exactly the value `read` sees. That is the closure equivalent of two methods on one instance, and it is how you add a read-only accessor.
- When would you prefer a class over a counter closure?As soon as the state must be inspected, reset, logged, serialised or extended. A closure's total is reachable only through the functions the factory returned, and closures cannot be pickled, so the accumulator cannot cross a process boundary. An object with an attribute is easier to test, easier to print in a debugger and open to subclassing.
saying these in an interview costs you the question
- Omits `nonlocal` and reports the counter as simply broken
- Says the inner assignment updates the outer variable automatically
- Thinks two calls to the factory share one total
- Uses `global` for state belonging to a single accumulator
- Claims captured variables are read-only in Python 3