Search code examples
pythonclosuresdecorator

Trying to Understand this bit of Python code on the topic of decorators


trying to understand this bit of code 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!


Solution

  • 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:

    1. Increment its own .count attribute
    2. Call the original foo function with whatever args you passed

    Since 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.