今天這篇文章,我們來學習一下 Python 的 Decorator 裝飾器。
Decorator 在 Python 是一個非常經典的功能,在工程中應用廣泛,例如日誌 (Log)、快取 (Cache)、多執行緒 (Threading) 等等。
Function Decorator
其實 Decorator 就是對函數的封裝,可以理解為在函數的前後做一點“裝飾”。我們會從 Python 的 lambda
切入講解,介紹 Decorator 的基本概念和用法,最後透過一個實際的例子加深理解。
前面說過,Python 的一切皆為物件,連函數也不例外。我們來看下面的例子:
1 | def func(): |
從上面的例子,我們把 func
作為一個變數賦值給 helloworld
,然後呼叫 helloworld
,相當於呼叫了 func
。所以我們也可以把函數作為一個參數傳到另一個函數裡面:
1 | def print_hello_world(): |
有了這些基礎概念後,我們接下來可以深入挖掘 Decorator。按照 Decorator 的思路,就是對某個函數做前後包裝,例如我們想要計算每個傳進來的函數執行花了多少時間,可以這樣寫:
1 | import time |
更通用一點,我們可以把 printer
封裝成更通用的函數直接返回:
1 | import time |
我們把原本的 print_hello_world
封裝成 decorator_benchmark
的內部函數,這樣在外面呼叫就會非常簡潔。不過這樣還是有點麻煩,如果我們總是需要對 print_hello_world
測量性能,呼叫之前都需要對它封裝一次,那有沒有更簡潔的方法呢?
1 | import time |
我們在 print_hello_world
上面加了 @decorator_benchmark
,其中 @
是 Python 裡的語法糖。我們可以對一些常見的功能,例如效能測量 (benchmark)、日誌 (log) 等等寫成一個 Decorator 函數,然後再對其他函數進行“裝飾”,這樣就大大提高了程式的重複利用性和可讀性。
當然,Decorator 具有強大的靈活性,我們也可以對其傳入參數,例如:
1 | def repeat(num): |
不過這樣寫有個副作用是,我們裝飾後的 print_hello_world
的元數據 (metadata) 就被改變了:
1 | help(print_hello_world) |
它告訴我們函數不再是原來的 print_hello_world
,而是被 wrapper
取代了。不過俗話說得好,見招拆招。為了解決這個問題,我們可以使用 Python 已有的 Decorator @functools.wraps
,它會保留原本函數的元數據(也就是將原本函數的元數據複製到 Decorator 裡面):
1 | import functools |
Class Decorator
最後來說說 Class Decorator。前面提到的 Decorator 是以函數為形式的,其實 class 也可以作為 Decorator,這樣可以持久化存一些資料。Class Decorator 藉由函數 __call__
,每當呼叫一次被裝飾的函數時,就會呼叫一次 __call__
。我們以“計算函數被呼叫的次數”作為例子:
1 | class Count: |
總結
所謂的 Decorator,就是透過去“裝飾”函數,增加或改變已有函數的功能,使得原有函數不需要修改。有如下優點:
- 封裝原有程式碼
- 程式碼簡潔
- 易讀