2017-07-07 140 views
1

我正在刷新我关于一些python特性的记忆,我没有得到,我从this python tutorial得知,并且有一个我不完全理解的例子。这是关于一个装饰计数调用一个函数,下面的代码:Python装饰器计数函数调用

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),而不是函数调用自己,为什么它实际上工作?

+0

'@call_counter succ' =='SUCC = call_counter(SUCC) =助手“ – Cheney

回答

1

要记住装饰重要的是,装饰是功能将函数作为参数,并返回另一个函数。返回值 - 又一个函数 - 是调用原始函数的名称时将调用的值。

这个模型可以很简单:

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

我在这里没有得到的是为什么我们增加函数包装器(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,这很好。

据我了解它的工作原理,因为它与helper功能与装饰(被重新定义它的每一个装饰作用时间)内替换功能succ所以succ = helpersucc.calls = helper.calls。 (尽管名称助手只在装饰者的名字空间中定义)

这是否有意义?

1

据我了解这一点(纠正我,如果我错了),你的程序执行的顺序是:

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

检查了这一点:

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