Where the count lives, and why the obvious place does not work.
def wrapper():
calls = 0
calls += 1 # a fresh local every call — always 1
# A closure variable survives, but is not readable from outside
def count_calls(function):
calls = 0
def wrapper(*args, **kwargs):
nonlocal calls
calls += 1
return function(*args, **kwargs)
return wrapper
# wrapper.calls -> AttributeError
# An attribute on the wrapper survives and is readable
wrapper.calls = 0
wrapper.calls += 1
- Functions have a __dict__, so you can hang state on them
wrapper.calls = 0 stores an attribute on the function object exactly as it would on any other object. That is what makes the count readable from outside without a class, a global, or an accessor — the decorated name *is* the wrapper, so double.calls reaches straight into it.
- nonlocal keeps the count private; an attribute publishes it
Both survive between calls. The difference is who can see them: a closure variable is reachable only through the wrapper’s own code, an attribute is part of the function’s public surface. Choosing the attribute here is a deliberate decision to make the tally part of the interface — which is also why it needs reset() to be part of it.
- Counting before the call is what includes the failures
Increment first and the tally records attempts; increment after the return and it records successes, silently missing everything that raised. For a 'how often is this running' question, attempts are the answer — and the ordering is the entire implementation of that choice.
Define the wrapper, then set an attribute on it before returning it. Inside the wrapper you can reach that same attribute by name, because the wrapper is in scope of itself.
wrapper.calls = 0 goes after the def and before the return. Inside, wrapper.calls += 1 as the first statement — first, so that a call which raises has already been counted.
reset is another attribute holding a small function: a def inside count_calls that sets wrapper.calls = 0, assigned as wrapper.reset. Decorate the wrapper with @functools.wraps(function) so the name and docstring survive — note that wraps copies attributes *from* the original, so set yours afterwards.
import functools def count_calls(function): @functools.____(function) def wrapper(*args, **kwargs): wrapper.calls += ____ return function(*args, **kwargs) def reset(): wrapper.calls = ____ wrapper.calls = 0 wrapper.reset = ____ return wrapper
import functools
def count_calls(function):
@functools.wraps(function)
def wrapper(*args, **kwargs):
# Counted before the call, so an invocation that raises still
# shows up in the tally.
wrapper.calls += 1
return function(*args, **kwargs)
def reset():
wrapper.calls = 0
wrapper.calls = 0
wrapper.reset = reset
return wrapper
wrapper.calls += 1 inside wrapper looks circular, and is not: the name is looked up when the line runs, by which time count_calls has finished and wrapper exists. The same mechanism that makes recursion work — names resolved at call time, not at definition time — is what makes this legal, and it means no nonlocal declaration is needed.
functools.wraps copies __name__, __doc__, __module__, __qualname__ and the original’s own __dict__ onto the wrapper. Since it updates the wrapper’s dictionary, anything you set first can be overwritten by an attribute of the same name on the original. Decorate first, then attach — which is what the ordering in the solution is quietly doing.
The increment before the call is the whole policy, and it is worth being explicit about which question you are answering. If you wanted successes and failures separately, the shape is a try/except that bumps two different counters — and that is usually the more useful version once anyone is looking at the numbers seriously.
For anything you intend to keep, use a real metrics library — prometheus_client’s Counter, statsd, OpenTelemetry — which give you labels, aggregation across processes and somewhere to look at the numbers. For finding out where the time goes rather than how often something runs, cProfile already counts every call in the program without you decorating anything. And in a test, unittest.mock’s call_count does this with no code at all. This decorator is for the ten minutes when you want one number and do not want to install anything — and note it is not thread-safe: += on an attribute is a read and a write, and two threads can lose an increment between them.
- Count successes and failures separately, and decide what .calls should then mean.
- Record the arguments of the last call as well, then work out why holding on to them is a memory leak waiting to happen.
- Make it safe to use from several threads with a lock, and measure how much the lock costs on a function that does almost nothing.