Reputation: 3
for some readability and searchability, here is the typed out code
def counter(func):
def wrapper(*args, **kwargs):
wrapper.count += 1
return func (*args, **kwargs)
wrapper.count = 0
return wrapper
@counter
def foo():
print('calling foo()')
foo()
foo()
print('foo() was called {} times.'.format(foo.count))
currently learning how decorator works...encounter this bit of code
couldn't understandwrapper.count = 0
and how when using the decorate in the following code, that the wrapper.count = 2
but not reset to 0
does this have anything to do with closure?
I visited other post which contains similar problem. the answer wasn't that clear to me
can anyone explain the logic behind this code?
Thanks!
Upvotes: 0
Views: 225
Reputation: 25489
In python, functions are first-class objects, which means that functions themselves are objects, and can be treated as such -- you can assign them to a variable, you can set attributes, etc.
So the line wrapper.count = 0
sets the count
attribute for the wrapper
object to the integer 0
. The fact that wrapper
is a function is irrelevant for now.
The way the wrapper
function is defined, every time you call wrapper
, the function increments its own count
attribute.
Now, when you use counter
as a decorator, the decorated function is replaced by the value that your decorator returned. It would be as if you did something like this:
def foo():
print("Calling foo()")
def counter(func):
def wrapper(*args, **kwargs):
wrapper.count += 1
func(*args, **kwargs)
wrapper.count = 0
return wrapper
# These two lines are equivalent to decorating foo with @counter
wrapping_func = counter(foo)
foo = wrapping_func
So when you call foo()
, you're actually calling the wrapper
function object that was returned when you decorated foo
with @counter
(i.e. wrapping_func
). This wrapper
does two things:
.count
attributefoo
function with whatever args you passedSince you called foo()
two times, the .count
was incremented two times
Later, when you access foo.count
, you're actually accessing the count
attribute for the wrapper
object that we talked about earlier (wrapping_func.count
), which is 2
.
Upvotes: 1
Reputation: 39
I haven't used python in a while myself, so I may be a bit rusty, but since there aren't any other answers, I'll see what I can do to help.
Functions in Python are first class, which means they are all actually classes under the hood. Think of def wrapper
as declaring a class rather than a function. The only difference is that it only has one method and its constructor is outside the class. wrapper.count = 0
is just serving as that constructor to let Python know that it has an extra property. It does look a little confusing to see that declared after the function though. Now that Python knows about wrapper.count
, that property can be accessed in the method. wrapper.count = 0
only runs once (as a constructor), but wrapper.count += 1
runs as many times as you call foo
. If there was another function decorated with @counter
, it would have a different count than foo
's counter because they are different instances of the same class. You can access foo.count
because it is not foo
anymore. Now it is a separate instance of counter
that happens to call foo
inside of it.
That may not use the fancy, technically more correct, language, but hopefully that helps.
Upvotes: 0