2017-07-07 143 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'