在你的例子中,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
結合clock
或rclock
。
@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,因爲較小的階乘從緩存中讀取而不是被重新計算。
「iterable」的意思是「遞歸」嗎? –
對不起,應該是遞歸的 –
只是對術語的評論,'clock'是一個裝飾器,只被調用一次。 'factorial'由'clock'裝飾器用'clocked'包裝器裝飾。所以這是每次被調用的'clocked'包裝器。它被多次調用的原因是因爲Python有後期綁定,所以對'factorial()'的遞歸調用在執行前並沒有綁定到一個函數上,到那個時候,'factorial = clock(factorial)',即一個裝飾'factorial'的形式。 – AChampion