[Python]對於 Decorator 裝飾器的理解
Decorator 裝飾器
程式設計中的「函式」是為了解決程式碼的重複利用、模組化,同時也增加可讀性。而 Python 的「裝飾器」可以在不改變原本函式的功能之下,又再進一步強化 (簡化) 不同函式之間程式碼的共用。
Python 的「函式」是所謂的「一級函式」,支援「高階函式」的用法,可被當成參數來傳遞。「裝飾器」就是一個「兩層」的函式,先接收一個函式進行包裝 (處理),包裝完再回傳新的函式。就是「吃進去又吐出來」的意思!
我簡單統整出兩種裝飾器函式的寫法:
# 針對沒有輸入/輸出的函式 def DecoratorName1(callback): def wrapper(): # Some Code... callback() # Some Code... return wrapper # 針對有輸入/輸出的函式 def DecoratorName2(callback): def wrapper(*args, **kwargs): # Some Code... result = callback(*args, **kwargs) # Some Code... return result return wrapper
使用裝飾器的方法:
# 定義 OriginalFunction 函式,並將它套用 DecoratorName 裝飾器 @DecoratorName def OriginalFunction(): # Some Code... # 執行原本的函式 OriginalFunction() 即可 OriginalFunction()
此處的 @ 是一個語法糖,相當於在定義完 OriginalFunction 之後,執行了一次:
OriginalFunction = DecoratorName(OriginalFunction)
所以後面執行的 OriginalFunction(),是被裝飾器 DecoratorName() 吃進去又吐出來的產物。
範例
我寫了一個範例,用一個「計時器」做為裝飾器,包裝一個「費波那契數列」的函數:
import time def timer(callback): """計時器-裝飾器""" def wrapper(*args, **kwargs): print(callback.__name__) # 看一下呼叫了哪個函式 start_time = time.time() callback(*args, **kwargs) elapsed_time = (time.time() - start_time) * 1000 print(f"\n{elapsed_time:.06f} ms") return wrapper # 用 timer() 包裝 fib() @timer def fib(n): """求費波那契數列""" a, b = 0, 1 while a < n: print(a, end=' ') a, b = b, a + b # 執行 fib() fib(1000) # 執行結果: # fib # 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 # 0.123456 ms
PS.如果沒加上 @timer 來包裝 fib() 函式,那麼執行 timer(fib)(1000) 也有一樣的結果。
再厲害一點,讓裝飾器也能傳入參數:
import time def showTimer(title): """計時器-裝飾器工廠""" def timer(callback): def wrapper(*args, **kwargs): print(callback.__name__) # 看一下呼叫了哪個函式 start_time = time.time() callback(*args, **kwargs) elapsed_time = (time.time() - start_time) * 1000 print(f"\n{title} {elapsed_time:.06f} ms") return wrapper return timer # 用 showTimer() 包裝 fib(),並於修飾器傳入參數 @showTimer("Elapsed Time:") def fib(n): """求費波那契數列""" a, b = 0, 1 while a < n: print(a, end=' ') a, b = b, a + b # 執行 fib() fib(1000) # 執行結果: # fib # 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 # Elapsed Time: 0.123456 ms
PS.如果沒加上 @showTimer() 來包裝 fib() 函式,那麼執行 showTimer("Elapsed Time:")(fib)(1000) 也有一樣的結果。
從程式碼可以看出,裝飾器函式由兩層變成了三層,這種做法稱為「Decorator Factory (裝飾器工廠)」。每多加一項功能又要多包一層,像洋蔥一樣。
如果不用 Decorator 裝飾器?
從裝飾器的原理來看,跟我寫一個函式,在函式裡呼叫另一個函式也是一樣的效果。或是在呼叫函式時,直接傳入另一個函式的執行結果,EX: func3(func1(), func2())。
像前面的程式,我一樣可以用兩個函式來處理:
import time def showTimerFunction(title, callback, *args, **kwargs): """計時器-函式""" print(callback.__name__) # 看一下呼叫了哪個函式 start_time = time.time() callback(*args, **kwargs) elapsed_time = (time.time() - start_time) * 1000 print(f"\n{title} {elapsed_time:.06f} ms") def fib(n): """求費波那契數列""" a, b = 0, 1 while a < n: print(a, end=' ') a, b = b, a + b # 執行 showTimerFunction() 去呼叫 fib() showTimerFunction("Elapsed Time:", fib, 1000) # 執行結果: # fib # 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 # Elapsed Time: 0.123456 ms
回歸原本函式呼叫函式的作法,這樣其實更不容易迷惑,要執行哪個函式還更有彈性。我可以決定要執行原本的 fib() 或是 showTimerFuncion(),而非只能執行被 showTimer() 包裝後的 fib()。
函式也不用一層一層的回傳自己。
如果不用傳遞參數,那函式寫起來還更簡單。
結論
目前還沒找到非得使用裝飾器的場景!
No comments yet.