Python 裝飾詞 (Decorator) 被大量廣泛的使用在各方 library,是非常實用和必須了解的基礎,本篇介紹了從原理到應用,建議讀者可以跟著敲一次 code,會對裝飾詞有更深入的了解唷~
Table
為什麼需要裝飾詞 decorator?
- 降低程式碼重複率
- 易讀性高
- 靈活度高
Python 裝飾詞的原理
▍開始前我們先了解:什麼是 變數名 與 函式物件 (object)
首先我們定義一個函式 def foo():
1 2 |
def foo(): return "bar" |
如果我們 print(foo),會得到 “function foo at 0x1028831e0” 表示 foo 是一個變數名,並且指向一個函式物件 (object) 。
如果要調用函式的話,則加上(), foo(),即可調用函式。
1 2 3 4 5 6 7 8 |
def foo(): return "bar" print(foo) # 變數名 print(foo()) # 函式物件 >>> <function foo at 0x1028831e0> >>> bar |
▍什麼是裝飾詞 decorator:
首先我們在 def timer(func): 將 func 變數名稱傳入,接下來定義 def wrap(sleep_time) 函式,並且在裡面將剛剛傳入的 func(sleep_time) 調用,這樣就完成一個簡單的裝飾詞囉!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import time def timer(func): def wrap(sleep_time): t_start = time.time() func(sleep_time) t_end = time.time() t_count = t_end - t_start print('[花費時間]', t_count) return wrap def dosomething(sleep_time): print('do some thing') time.sleep(sleep_time) foo = timer(dosomething) foo(3) >>> do some thing >>> [花費時間] 3.004279136657715 |
1. 語法糖 (Syntax Candy)
如果不想寫成 foo = timer(dosomething),只需要加上 @timer,並直接調用 dosomething() 函式執行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
def timer(func): def wrap(sleep_time): t_start = time.time() func(sleep_time) t_end = time.time() t_count = t_end - t_start print(‘[花費時間]’, t_count) return wrap @timer def dosomething(sleep_time): print(‘do some thing’) time.sleep(sleep_time) dosomething(3) >>> do some thing >>> [花費時間] 3.003619909286499 |
2. 裝飾詞的副作用:函式名稱
裝飾詞在被 wrap 包一層後,其 __name__ 屬性就會被修改成 wrap
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
def timer(func): def wrap(): t_start = time.time() func() t_end = time.time() t_count = t_end - t_start print(‘[花費時間]’, t_count) return wrap @timer def dosomething(): print('do some thing') dosomething() print(dosomething.__name__) >>> wrap |
如果要消除這個副作用的話,可以使用 python 內建的 functools,只需要在 def wrap()之前,加上 @wraps(func),即可獲得原先的 __name__ 屬性 dosomething。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
from functools import wraps def timer(func): @wraps(func) def wrap(): t_start = time.time() func() t_end = time.time() t_count = t_end - t_start print(‘[花費時間]’, t_count) return wrap @timer def dosomething(): print('do some thing') dosomething() print(dosomething.__name__) >>> dosomething |
3. 裝飾詞的觸發先後順序
如果有兩個 Decorator 裝飾詞要使用的話怎麼辦,其實只需要加在上面一行即可,順序的話會從上而下觸發
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
from functools import wraps def timer(func): @wraps(func) def wrap(): t_start = time.time() func() t_end = time.time() t_count = t_end - t_start print(‘[花費時間]’, t_count) return wrap def func_print_one(func): @wraps(func) def wrap(): print(‘this is func_print_one’) func() return wrap def func_print_two(func): @wraps(func) def wrap(): print('this is func_print_two') func() return wrap @timer @func_print_one @func_print_two def dosomething(): print('do some thing') dosomething() >>> this is func_print_one >>> this is func_print_two >>> do some thing >>> [花費時間] 2.3126602172851562e-05 |
4. 裝飾詞的參數 (*args, **kargs)
只需在 def wrap() 和 func() 中加入 *args, **kargs 即可調用參數,範例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
from functools import wraps def timer(param: str): def timer_func(func): @wraps(func) def wrap(*args, **kargs): t_start = time.time() print(param) value = func(*args, **kargs) t_count = time.time() - t_start print(f"Function '{func.__name__}' spend: {t_count} s") return value return wrap return timer_func @timer("Print before function start") def dosomething(a, b): print(f"Count: {a + b}") dosomething(1, 2) > Print before function start > Count: 3 > Function 'dosomething': 4.410743713378906e-05 s |
5. 用 Class 寫裝飾詞
如果是 Class 的方法來寫裝飾詞的話,會將 wrap 寫在 __call__ 裡面來調用,範例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
from functools import wraps class Timer: def __init__(self, time_sleep): print('[__init__]') print('[time_sleep]:', time_sleep) self.time_sleep = time_sleep def __call__(self, func): @wraps(func) def wrap(*args, **kargs): t_start = time.time() time.sleep(self.time_sleep) value = func(*args, **kargs) t_end = time.time() t_count = t_end - t_start print('[共花費時間]', t_count) return value return wrap @Timer(time_sleep=3) def dosomethingClass(a, b): print('do some thing') print('a + b = ', a + b) dosomethingClass(1, 2) >>> [__init__] >>> [time_sleep]: 3 >>> do some thing >>> a + b = 3 >>> [共花費時間] 3.0002880096435547 |
本篇簡單的介紹了裝飾詞的原理、語法糖的使用、函式名稱的副作用、觸發的先後順序、帶參數和如何使用 Class來寫裝飾詞,建議可以實際將 code 打一次,會對裝飾詞有更深的了解唷~
關於 Python 物件導向教學的延伸閱讀:
▍本站的其他相關教學:
- [Python教學] 一切皆為物件,到底什麼是物件 Object ?
- [Python教學] 物件導向 – Class 類的 封裝 / 繼承 / 多型
- [Python教學] Class / Static / Abstract Method 初探
- [Python教學] @property 是什麼? 使用場景和用法介紹
- [Python教學] 裝飾詞原理到應用
- [Python教學] dataclass 是什麼? (python 3.7+)
那麼 [Python教學] 裝飾詞原理到應用 結束囉,感謝收看!如有任何問題,歡迎底下留言或私訊,我會盡快回覆您