At a high level, the decorated function maintains a counter of how many times it was called.
There is one major issue with the code. The wrapper does not actually call the wrapped function as it should. Instead of return func
, which just returns the function object, it should read
return func(*args, **kwargs)
As @warvariuc points out, one possible reason is that the author did not have or did not know about nonlocal
, which lets you access the enclosing namespace.
I think a more plausible reason is that you want to be able to access the counter. Functions are first-class objects with mutable dictionaries. You can assign and access arbitrary attributes on them. It could be convenient to check foo.count
after a few calls, otherwise why maintain it in the first place?
The reason that wrapper.counter
is initialized the way it is is simply that wrapper
does not exist in the local namespace until the def
statement runs to create it. A new function object is made by the inner def
every time you run counter
. def
generally is an assignment that creates a function object every time you run it.
One more minor point about the code you show is that foo.__name__
will be wrapper
instead of foo
after the decoration. To mitigate this, and make it mimic the original function more faithfully, you can use functools.wraps
, which is a decorator for the decorator wrappers. Your code would look like this:
from functools import wraps
def counter(func):
@wraps(func)
def wrapper(*args, **kwargs):
wrapper.count += 1
# Call the function being decorated and return the result
return func(*args, **kwargs)
wrapper.count = 0
# Return the new decorated function
return wrapper
# Decorate foo() with the counter() decorator
@counter
def foo():
print('calling foo()')
Now you can do
>>> foo.__name__
'foo'
>>> foo()
calling foo()
>>> foo()
calling foo()
>>> foo()
calling foo()
>>> foo.count
3