假設我們需要一個可以輸出某個函數運行時長的裝飾器。
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)
logging.info('[%0.8fs] %s(%s) -> %r', elapsed, name, arg_str, result)
return result
return clocked
這裏利用函數裝飾器,在 clock(func) 函數內部定義了一個 clock(*args) 函數,定義好後直接返回。內部利用 perf_count() 函數實現計算函數運行時長。每調用一次 perf_counter(),Python 就會記錄一個時間點,類似於在秒錶上按下開始計時鍵;當第二次調用該函數時,會計算與第一個時間點的時間長,類似於在秒錶上按下結束計時鍵1。
func.__name__
會返回入參函數的名稱。repr(arg) 會返回一個 arg的 string 格式2。通過一系列轉換,我們就可以得到一個以逗號作爲分隔符的入參字符串。
內部函數最後以這樣的一種格式 [時長] 運行函數名(多個入參字符串) -> 輸出結果
輸出函數運行報告。其中的 %0.8fs 表示小數保留8位,然後再轉換爲字符串。
而外部函數最後返回這個 clocked(*args) 函數。
接着我們使用這個 clock 裝飾器,來輸出以下兩個函數的運行報告:
- 睡眠函數;
- 斐波那契函數。
import time
from course_6.clock_decorator import clock
@clock
def snooze(seconds):
time.sleep(seconds)
@clock
def factorial(n):
return 1 if n < 2 else n * factorial(n - 1)
if __name__ == '__main__':
logging.info('snooze(.123) -> %s', snooze(.123))
logging.info('factorial(6) -> %s', factorial(6))
運行結果:
這裏使用 @裝飾函數名
這樣的語法來包裝我們需要運行的函數。
@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)
所以從寫法上來講,第一種方式更加簡潔。
如果輸出 logging.info('factorial.__name__ -> %s',factorial.__name__)
就會得到 factorial.__name__ -> clocked
。這就說明了 factorial 實際上是 clocked 函數,也就是說factorial 函數已經被裝飾爲 clocked 函數。所以每次調用 factorial 函數,本質上就是調用 clocked 函數。
2 優化
前面說了,如果輸出 logging.info('factorial.__name__ -> %s',factorial.__name__)
就會得到 factorial.__name__ -> clocked
。也就是說,裝飾函數 clock(func) 把 factorial(n) 函數給遮住了。如果我們不想被裝飾函數的 __name__
屬性被遮住,可以這樣做:
@functools.wraps 也是一個裝飾器,它可以把 func 中的相關屬性複製到 clocked 中。這樣再次輸出logging.info('factorial.__name__ -> %s',factorial.__name__)
就會得到 factorial.__name__ -> factorial
咯。
這實際上就是經典的裝飾器設計模式,但在是實現方式上與普通的面嚮對象語言差別較大。普通的面嚮對象語言採用的是面向對象的編程方式,而 Python 採用的是面向函數的編程方式。
- Python3 perf_counter() 用法.
- Python repr() 函數.
- Luciano Ramalho (作者),安道,吳珂 (譯者).流暢的Python[M].人民郵電出版社,2017:319-322.