2017-04-16 36 views
1
import time 
def clock(func): 
    def clocked(*args): 
     t0 = time.perf_counter() 
     result = func(*args) 
     elapsed = time.perf_counter() - t0 
     name = func.__name__ 
     arg_str = ', '.join(repr(arg) for arg in args) 
     print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result)) 
     return result 
    return clocked 

這是裝飾工。當參數是遞歸函數時,裝飾器如何工作?

@clock 
def factorial(n): 
    return 1 if n < 2 else n*factorial(n-1) 

結果的部分是:

[0.00000191s] factorial(1) -> 1 
[0.00004911s] factorial(2) -> 2 
[0.00008488s] factorial(3) -> 6 
[0.00013208s] factorial(4) -> 24 
[0.00019193s] factorial(5) -> 120 
[0.00026107s] factorial(6) -> 720 
6! = 720 

這個裝飾時的說法是遞歸函數是如何工作的?爲什麼裝飾器可以被執行很多次。怎麼運行的?

+2

「iterable」的意思是「遞歸」嗎? –

+0

對不起,應該是遞歸的 –

+0

只是對術語的評論,'clock'是一個裝飾器,只被調用一次。 'factorial'由'clock'裝飾器用'clocked'包裝器裝飾。所以這是每次被調用的'clocked'包裝器。它被多次調用的原因是因爲Python有後期綁定,所以對'factorial()'的遞歸調用在執行前並沒有綁定到一個函數上,到那個時候,'factorial = clock(factorial)',即一個裝飾'factorial'的形式。 – AChampion

回答

3

在你的例子中,clock裝飾器被執行一次,它用時鐘版本替換原始版本factorial。原始factorial是遞歸的,因此裝飾版本也是遞歸的。因此,您可以爲每次遞歸調用打印計時數據 - 裝飾的factorial自己調用,而不是原始版本,因爲名稱factorial現在指的是裝飾版本。


在修飾器中使用functools.wraps是一個好主意。這將原始函數的各種屬性複製到裝飾版本。

例如,在不wraps

import time 

def clock(func): 
    def clocked(*args): 
     ''' Clocking decoration wrapper ''' 
     t0 = time.perf_counter() 
     result = func(*args) 
     elapsed = time.perf_counter() - t0 
     name = func.__name__ 
     arg_str = ', '.join(repr(arg) for arg in args) 
     print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result)) 
     return result 
    return clocked 

@clock 
def factorial(n): 
    ''' Recursive factorial ''' 
    return 1 if n < 2 else n * factorial(n-1) 

print(factorial.__name__, factorial.__doc__) 

輸出

clocked Clocking decoration wrapper 

隨着wraps

import time 
from functools import wraps 

def clock(func): 
    @wraps(func) 
    def clocked(*args): 
     ''' Clocking decoration wrapper ''' 
     t0 = time.perf_counter() 
     result = func(*args) 
     elapsed = time.perf_counter() - t0 
     name = func.__name__ 
     arg_str = ', '.join(repr(arg) for arg in args) 
     print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result)) 
     return result 
    return clocked 

@clock 
def factorial(n): 
    ''' Recursive factorial ''' 
    return 1 if n < 2 else n * factorial(n-1) 

print(factorial.__name__, factorial.__doc__) 

輸出

factorial Recursive factorial 

這就是我們得到的,如果我們在未裝飾版本上做了print(factorial.__name__, factorial.__doc__)


如果你不想clock -decorated遞歸函數打印定時信息的所有遞歸調用,它變得有點棘手。

最簡單的方法是不使用的修飾語法,只是叫clock作爲一個正常的功能,所以我們的時鐘版的功能得到一個新的名字:

def factorial(n): 
    return 1 if n < 2 else n * factorial(n-1) 

clocked_factorial = clock(factorial) 

for n in range(7): 
    print('%d! = %d' % (n, clocked_factorial(n))) 

輸出

[0.00000602s] factorial(0) -> 1 
0! = 1 
[0.00000302s] factorial(1) -> 1 
1! = 1 
[0.00000581s] factorial(2) -> 2 
2! = 2 
[0.00000539s] factorial(3) -> 6 
3! = 6 
[0.00000651s] factorial(4) -> 24 
4! = 24 
[0.00000742s] factorial(5) -> 120 
5! = 120 
[0.00000834s] factorial(6) -> 720 
6! = 720 

另一種方法是將遞歸函數包裝在非遞歸函數中,並將裝飾器應用於新函數。

def factorial(n): 
    return 1 if n < 2 else n * factorial(n-1) 

@clock 
def nr_factorial(n): 
    return factorial(n) 

for n in range(3, 7): 
    print('%d! = %d' % (n, nr_factorial(n))) 

輸出

[0.00001018s] nr_factorial(3) -> 6 
3! = 6 
[0.00000799s] nr_factorial(4) -> 24 
4! = 24 
[0.00000801s] nr_factorial(5) -> 120 
5! = 120 
[0.00000916s] nr_factorial(6) -> 720 
6! = 720 

另一種方法是修改裝飾,使其保持它無論是在執行遞歸或內部級別的一個頂層的軌道,只有打印頂層的時間信息。這個版本使用nonlocal指令所以它只能在Python 3的作品,而不是Python 2

def rclock(func): 
    top = True 
    @wraps(func) 
    def clocked(*args): 
     nonlocal top 
     if top: 
      top = False 
      t0 = time.perf_counter() 
      result = func(*args) 
      elapsed = time.perf_counter() - t0 
      name = func.__name__ 
      arg_str = ', '.join(repr(arg) for arg in args) 
      print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result)) 
     else: 
      result = func(*args) 
      top = True 
     return result 
    return clocked 

@rclock 
def factorial(n): 
    return 1 if n < 2 else n * factorial(n-1) 

for n in range(3, 7): 
    print('%d! = %d' % (n, factorial(n))) 

輸出

[0.00001253s] factorial(3) -> 6 
3! = 6 
[0.00001205s] factorial(4) -> 24 
4! = 24 
[0.00001227s] factorial(5) -> 120 
5! = 120 
[0.00001422s] factorial(6) -> 720 
6! = 720 

rclock功能可以在非遞歸函數中使用,但它是一個使用原始版本clock效率更高一點。


functools另一個方便的功能,你應該知道,如果你使用遞歸函數是lru_cache。這保留了最近計算結果的緩存,因此不需要重新計算。這可以極大地加快遞歸功能。有關詳細信息,請參閱文檔。

我們可以使用lru_cache結合clockrclock

@lru_cache(None) 
@clock 
def factorial(n): 
    return 1 if n < 2 else n * factorial(n-1) 

for n in range(3, 7): 
    print('%d! = %d' % (n, factorial(n))) 

輸出

[0.00000306s] factorial(1) -> 1 
[0.00017850s] factorial(2) -> 2 
[0.00022049s] factorial(3) -> 6 
3! = 6 
[0.00000542s] factorial(4) -> 24 
4! = 24 
[0.00000417s] factorial(5) -> 120 
5! = 120 
[0.00000409s] factorial(6) -> 720 
6! = 720 

正如你可以看到,即使我們使用了純clock裝飾僅定時信息的單行獲取打印爲4,5的階乘,和6,因爲較小的階乘從緩存中讀取而不是被重新計算。

+0

是否表示裝飾函數是遞歸的,並且會多次調用?並每次打印信息? –

+0

@DeanWang我剛剛給我的答案添加了更多信息。希望對你有幫助。 –

+0

非常感謝您的回答 –

1

當您將一個裝飾器應用於一個函數時,該函數作爲參數傳遞給裝飾器。函數是遞歸還是不是無關緊要。

代碼

@clock 
def factorial(n): 
    return 1 if n < 2 else n*factorial(n-1) 

相當於

def factorial(n): 
    return 1 if n < 2 else n*factorial(n-1) 
factorial = clock(factorial) 
+0

我很困惑爲什麼函數可以多次作爲參數傳遞給裝飾器。 –

+2

@DeanWang函數每次調用函數時只傳遞給裝飾器一次 –

+0

,裝飾器會被執行,對不對?遞歸被調用了很多次,裝飾器被執行了很多次,對吧? –

0

裝飾函數傳遞給裝飾作爲參數並返回另一個函數來代替原來的,返回的功能是沒有遞歸函數,但是當你調用它時,它會調用原始的遞歸函數:

def clock(func): 
    def clocked(*args): 
    t0 = time.perf_counter() 
    result = func(*args) # You call your original recursive function here 
    elapsed = time.perf_counter() - t0 
    name = func.__name__ 
    arg_str = ', '.join(repr(arg) for arg in args) 
    print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result)) 
    return result 
return clocked 

當你打電話給你的裝飾功能factorial,你居然叫是clocked,它實際上調用下面一行factorial

result = func(*args) 

裝飾器只執行一次。

的理解,你可以把你的功能變得@clock後下列之一:

def factorial(*args): 
    def _factorial(n): 
     return 1 if n < 2 else n*_factorial(n-1) 
    t0 = time.perf_counter() 
    result = _factorial(*args) 
    elapsed = time.perf_counter() - t0 
    name = _factorial.__name__ 
    arg_str = ', '.join(repr(arg) for arg in args) 
    print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result)) 
    return result 
+0

表示裝飾函數是遞歸的,它會被多次調用?並每次打印信息? –

+0

裝飾後的功能會多次調用,就像它沒有裝飾並正常調用一樣。打印信息的邏輯在你的包裝函數'clock'中,它只會被調用一次。 – shizhz

+0

@DeanWang,我更新了我的答案,以便於理解,查看出 – shizhz

0

也許它可以幫助承擔的視圖中的「語法糖」點。 這是從PEP 318具有修飾(I簡化的例子)

當前語法功能裝飾如在Python 2.4a2實現是:

@dec 
def func(arg1, arg2, ...): 
    pass 

這相當於:

def func(arg1, arg2, ...): 
    pass 
func = dec(func) 

正如你所看到的,裝飾器函數只被調用一次,它返回的包裝器被分配給裝飾函數的名稱。因此,無論何時通過其名稱調用原始函數(例如在遞歸中),都會調用包裝器(而不是裝飾器函數)。

相關問題