Python decorators count function call

后端 未结 4 954
长发绾君心
长发绾君心 2020-12-10 15:54

I\'m refreshing my memory about some python features that I didn\'t get yet, I\'m learning from this python tutorial and there\'s an example that I don\'t fully understand.

相关标签:
4条回答
  • 2020-12-10 16:31

    The important thing to remember about decorators is that a decorator is a function that takes a function as an argument, and returns yet another function. The returned value - yet another function - is what will be called when the name of the original function is invoked.

    This model can be very simple:

    def my_decorator(fn):
        print("Decorator was called")
        return fn
    

    In this case, the returned function is the same as the passed-in function. But that's usually not what you do. Usually, you return either a completely different function, or you return a function that somehow chains or wraps the original function.

    In your example, which is a very common model, you have an inner function that is returned:

    def helper(x):
        helper.calls += 1
        return func(x)
    

    This inner function calls the original function (return func(x)) but it also increments the calls counter.

    This inner function is being inserted as a "replacement" for whatever function is being decorated. So when your module foo.succ() function is looked up, the result is a reference to the inner helper function returned by the decorator. That function increments the call counter and then calls the originally-defined succ function.

    0 讨论(0)
  • 2020-12-10 16:34

    When you decorate a function you "substitute" you're function with the wrapper.

    In this example, after the decoration, when you call succ you are actually calling helper. So if you are counting calls you have to increase the helper calls.

    You can check that once you decorate a function the name is binded tho the wrapper by checking the attribute __name__ of the decorated function:

    def call_counter(func):
        def helper(*args, **args):
            helper.calls += 1
            return func(*args, **args)
        helper.calls = 0
        return helper
    
    @call_counter
    def succ(x):
        return x + 1
    
    succ(0)
    succ(1)
    print(succ.__name__)
    >>> 'helper'
    print(succ.calls)
    >>> 2
    
    0 讨论(0)
  • 2020-12-10 16:47

    As I understand this (correct me if I'm wrong) the order you program executes is:

    1. Register call_function.
    2. Register succ.
    3. While registering succ function interpreter finds a decorator so it executes call_function.
    4. Your function returns an object which is a function (helper). And adds to this object field calls.
    5. Now your function succ has been assigned to helper. So when you call your function, you're actually calling helper function, wrapped within a decorator. So every field you add to your helper function is accessible outside by addressing succ because those 2 variables refer to same thing.
    6. So when you call succ() it's basically the same if you would do helper(*args, **argv)

    Check this out:

    def helper(x):
        helper.calls += 1
        return 2
    helper.calls = 0
    
    def call_counter(func):
        return helper
    
    @call_counter
    def succ(x):
        return x + 1
    
    if __name__ == '__main__':
        print(succ == helper)  # prints true.
    
    0 讨论(0)
  • 2020-12-10 16:50

    What I don't get here is why do we increment the calls of the function wrapper (helper.calls += 1) instead of the function calls itself, and why does it actually working?

    I think to make it a generically useful decorator. You could do this

    def succ(x):
        succ.calls += 1
        return x + 1
    
    if __name__ == '__main__':
        succ.calls = 0
        print(succ.calls)
        for i in range(10):
            print(succ(i))
        print(succ.calls)
    

    which works just fine, but you would need to put the .calls +=1 in every function you wanted to apply this too, and initialise to 0 before you ran any of them. If you had a whole bunch of functions you wanted to count this is definitely nicer. Plus it initialises them to 0 at definition, which is nice.

    As i understand it it works because it replaces the function succ with the helper function from within the decorator (which is redefined every time it decorates a function) so succ = helper and succ.calls = helper.calls. (although of course the name helper is only definied within the namespace of the decorator)

    Does that make sense?

    0 讨论(0)
提交回复
热议问题