Python装饰器计数函数调用

时间:2017-07-07 10:01:23

标签: python python-decorators

我正在刷新一些我尚未得到的python功能,我正在学习this python tutorial并且有一个我不完全理解的例子。它是关于计算函数调用的装饰器,这里是代码:

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

@call_counter
def succ(x):
    return x + 1

if __name__ == '__main__':
    print(succ.calls)
    for i in range(10):
        print(succ(i))
    print(succ.calls)

我没有得到的是为什么我们增加函数包装器的调用(helper.calls + = 1)而不是函数调用本身,为什么它实际上工作?

4 个答案:

答案 0 :(得分:4)

要记住关于装饰器的重要一点是装饰器是一个函数,它将一个函数作为参数,并返回另一个函数。返回的值 - 另一个函数 - 是在调用原始函数的名称时调用的。

这个模型非常简单:

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

在这种情况下,返回的函数与传入函数相同。但这通常不是你做的。通常,您返回一个完全不同的函数,或者返回一个以某种方式链接或包装原始函数的函数。

在您的示例中,这是一个非常常见的模型,您将返回一个内部函数:

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

此内部函数调用原始函数(return func(x)),但它也会增加调用计数器。

这个内部功能正被插入作为替换"无论正在装饰什么功能。因此,当查找模块foo.succ()函数时,结果是对装饰器返回的内部辅助函数的引用。该函数递增调用计数器,然后调用最初定义的succ函数。

答案 1 :(得分:1)

  

我不能在这里得到的是为什么我们增加函数包装器的调用(helper.calls + = 1)而不是函数调用本身,为什么它实际上有效?

我认为要使它成为一个非常有用的装饰者。你可以这样做

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)

工作正常,但你需要将.calls +=1放在你想要应用它的每个函数中,并在运行任何函数之前初始化为0。如果你想要计算一大堆功能,那肯定会更好。另外,它在定义时将它们初始化为0,这很不错。

据我了解它可行,因为它用装饰器中的succ函数替换了函数helper(每次装饰函数时都重新定义)所以succ = helper和{ {1}}。 (当然,名称助手只在装饰器的命名空间内定义)

这有意义吗?

答案 2 :(得分:1)

据我所知(如果我错了,请纠正我)你编程执行的命令是:

  1. 注册call_function
  2. 注册succ
  3. 注册succ函数解释器时会找到一个装饰器,因此它会执行call_function
  4. 您的函数返回一个函数(helper)。并添加到此对象字段calls
  5. 现在,您的功能succ已分配到helper 。因此,当你调用你的函数时,你实际上正在调用helper函数,包装在装饰器中。因此,通过寻址succ可以在外部访问您添加到辅助函数的每个字段,因为这两个变量引用相同的内容。
  6. 所以当你致电succ()时,如果你做helper(*args, **argv)
  7. ,它基本相同

    检查出来:

    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.
    

答案 3 :(得分:1)

当你装饰一个功能时,你可以替换"你可以使用包装器。

在此示例中,在装饰后,当您致电succ时,您实际上正在呼叫helper。因此,如果您正在计算呼叫,则必须增加helper次呼叫。

您可以通过检查已修饰函数的属性__name__来检查一旦您修饰函数,该名称与包装器绑定在一起:

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

@call_counter
def succ(x):
    return x + 1

print(succ.__name__)
>>> 'helper'