- How can I reference a function inside itself (wrapper.count)?
Function bodies are executed only when you call it. By that time, the function is already defined so this is what makes that possible. The following will not give you any error, unless you call it:
>>> def foo():
... non_existing_function()
...
And whenever you enter the body of foo
, foo
is already defined, so you can reference it. This is also what makes recursive calls possible.
- How wrapper has the method count before wrapper is defined?
The question could also be "How could I increment the wrapper.count
before it is initialized?"
But again, we can answer this in the same way: since function bodies are not executed until we call them, wrapper.count
is initialized to 0 before wrapper.count += 1
.
- Shouldn't the line wrapper.count = 0 be executed everytime I call foo()?
Let's look at what is happening. You have written:
@counter
def foo():
print('calling foo()')
which is just a syntactic sugar for this:
foo = counter(foo)
Now, we are calling counter
function with foo
as an argument. What counter
does?
def counter(func):
def wrapper(*args, **kwargs):
wrapper.count += 1
# Call the function being decorated and return the result
return wrapper.count
wrapper.count = 0
# Return the new decorated function
return wrapper
In human language,
- define a function named
wrapper
which takes unknown number of positional and keyword arguments
- assign
0
as an attribute named count
for wrapper
function
- return
wrapper
to the caller
And when we assign the result back to the foo
function, we've actually assigned wrapper
to foo
. So when we call foo
, we are actually calling wrapper
. The line wrapper.count = 0
is outside wrapper
function so it will not run every time we call foo
.
Lastly, I would highly recommend you watching great PyCon talk by Reuven M. Lerner about decorators.
Edit: I didn't read the body of the wrapper, which actually proves that you don't really need to know what is inside the wrapper. My explanations are still correct. But, as it is suggested in @Mark Tolonen's answer, your wrapper should probably return func(*args,**kwargs)
not wrapper.count