- 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