測試環境為 CentOS 8 x86_64 (虛擬機)
關於 Syntactic sugar 請參考 – https://zh.wikipedia.org/zh-tw/%E8%AF%AD%E6%B3%95%E7%B3%96
語法糖(英語:Syntactic sugar)是由英國電腦科學家彼得·蘭丁發明的一個術語,指電腦語言中添加的某種語法,這種語法對語言的功能沒有影響,但是更方便程式設計師使用。語法糖讓程式更加簡潔,有更高的可讀性。
在 Python 的 syntax candy(syntactic sugar) 被稱作 Decorator (使用 @ 當做 syntax candy),這邊來看一下 Decorator(裝飾器) 與 wrapper 怎麼來使用.
參考範例 – https://dev.to/codemee/python-decorator-3bni
無使用 Decorator (裝飾器)
- 範例 : 建立一個從 1 加到 10000 的函數.
[root@localhost ~]# vi NoDecorator1.py def works(): total = 0 for i in range(10000): total += i print("total:", total) works()
執行結果
[root@localhost ~]# python3 NoDecorator1.py total: 49995000
- 範例 : 建立一個從 1 加到 10000 的函數與其計算其執行時間.
[root@localhost ~]# vi NoDecorator2.py import time def timing(func): print("Start...") t1 = time.perf_counter() func() t2 = time.perf_counter() print("Elapsed time(secs):", t2 - t1) def works(): total = 0 for i in range(10000): total += i print("total:", total) timing(works)
執行結果
[root@localhost ~]# python3 NoDecorator2.py Start... total: 49995000 Elapsed time(secs): 0.0006550069992954377
說明:
匯入時間模組.import time
回傳當下時間,格式為 float 浮點數.
time.perf_counter()
在 Python 中的 function 函數可以當成參數傳遞並執行,透過這種方式可以重覆使用相同的程式碼.
範例中會去執行 timing 函數並帶 work 函數為參數.
timing(works)
在 timing 函數的傳入值是 func 函數(這個名稱可以自己喜歡來命名, funabc , test123 …),而實際對應到的就是前面呼叫時所使用的 works 函數.
def timing(func):
在 timing 函數後面有呼叫 func (即是 works 函數).
func()
使用 Decorator (裝飾器)
- 範例 : 使用 包裝函數 wrapper 把原本函數包裝起來
Decorator (裝飾器)是 Python 中的一種設計模式,它允許用戶在不修改其結構的情況下向現有對象添加新功能.裝飾器允許我們 wrap (包裝)另一個函數以擴展包裝函數的行為,而無需永久修改它.[root@localhost ~]# vi Decorator1.py import time def timing(func): def wrapper(): print("Start...") t1 = time.perf_counter() func() t2 = time.perf_counter() print("Elapsed time(secs):", t2 - t1) return wrapper def work100(): total = 0 for i in range(100): total += i print("1 to 100 total:", total) def work10000(): total = 0 for i in range(10000): total += i print("1 to 10000 total:", total) work1 = timing(work100) work1() print ('----------') work2 = timing(work10000) work2()
執行結果
[root@localhost ~]# python3 Decorator1.py Start... 1 to 100 total: 4950 Elapsed time(secs): 1.1469999662949704e-05 ---------- Start... 1 to 10000 total: 49995000 Elapsed time(secs): 0.00045819100023436476
說明:
我們在 timing 函數使用了包裝函數 wrapper (名稱自訂,可以為 wrap123 , 123 .. 回傳相同名稱) 把不同函數包裝起來.這就是 Decorator function 裝飾函數.def wrapper(): return wrapper
使用上需要先包裝成新的函數 work1 ,包含 timing 函數與 work100 函數,再去執行 work1 函數.
work1 = timing(work100) work1()
work2 函數一樣是透過包裝的方式把 timing 函數與 work10000 函數包裝 wrap 再一起.
work2 = timing(work10000) work2()
- 範例 : 裝飾器 (decorator) 的 語法糖 (syntax sugar)
透過 @ 語法糖 (syntax sugar) 可以簡化上面的範例.[root@localhost ~]# vi Decorator2.py import time def timing(func): def wrapper(): print("Start...") t1 = time.perf_counter() func() t2 = time.perf_counter() print("Elapsed time(secs):", t2 - t1) return wrapper @timing def works(): total = 0 for i in range(10000): total += i print("total:", total) works()
執行結果
[root@localhost ~]# python3 Decorator2.py Start... total: 49995000 Elapsed time(secs): 0.0006422139999813226
說明:
其中的 @ 就是 syntax sugar , 這個動作等同下面,在呼叫 works 涵式前會先傳入 timing 涵式,並把回傳的涵式命明為同名的 works 函數.@timing def works():
works = timing(works)
當有多個 syntax sugar 執行的順序將會 由下而上,下面 b 函數會先執行 @a3 然後 @a2 最後才是 @a1.
@a1 @a2 @a3 def b():
有參數的 Function (函數) 與 Decorator (裝飾器)
- 範例 : 有參數的函數
[root@localhost ~]# vi Decorator3.py import time def timing(func): def wrapper(*args, **kwargs): print("Start...") t1 = time.perf_counter() func(*args, **kwargs) t2 = time.perf_counter() print("Elapsed time(secs):", t2 - t1) return wrapper @timing def works(start, stop): total = 0 for i in range(start, stop): total += i print("total:", total) works(1, 10000)
執行結果
[root@localhost ~]# python3 Decorator3.py Start... total: 49995000 Elapsed time(secs): 0.0005442179999590735
說明:
*args 和 **kwargs 表示為任意數量的參數.(*args, **kwargs)
再把這些參數轉傳到被包裝的函數.
func(*args, **kwargs)
- 範例 : 有參數的函數 (使用 functools 的 wraps)
上面的範例會有一個問題(回傳的函式名稱不是原本的函式),檢視一下 works(在 Decorator3.py 最後面加上以下) .print(works)
執行結果,明明是 works 卻顯示為 timing 函式.
[root@localhost ~]# python3 Decorator3.py <function timing.<locals>.wrapper at 0x7f5b4fdb0840>
這是因為裝飾詞在被 wrap 之後 __name__ 屬性就會變成 wrap , 可以已透 python 內建的 functools 的 wraps 避免這個問題.
[root@localhost ~]# vi Decorator4.py import time from functools import wraps def timing(func): @wraps(func) def wrapper(*args, **kwargs): print("Start...") t1 = time.perf_counter() func(*args, **kwargs) t2 = time.perf_counter() print("Elapsed time(secs):", t2 - t1) return wrapper @timing def works(start, stop): total = 0 for i in range(start, stop): total += i print("total:", total) print(works)
執行結果
[root@localhost ~]# python3 Decorator4.py <function works at 0x7faae736d1e0>
說明:
跟前面程式差別只有以下兩行.
從 functools 匯入 wrapsfrom functools import wraps
避免 wrap 之後 __name__ 屬性就會變成 wrap 這個問題.
@wraps(func)
- 範例 : 有參數的 Decorator (裝飾器)
[root@localhost ~]# vi Decorator5.py import time def timing(times): def outer(func): def wrapper(*args, **kwargs): print("Start...") t1 = time.perf_counter() for i in range(times): func(*args, **kwargs) t2 = time.perf_counter() print("Elapsed time(secs):", t2 - t1) return wrapper return outer @timing(2) def works(start, stop): total = 0 for i in range(start, stop): total += i print("total:", total) works(1, 10000)
執行結果
[root@localhost ~]# python3 Decorator5.py Start... total: 49995000 total: 49995000 Elapsed time(secs): 0.0010434840005473234
說明:
需要參數的裝飾器需要再使用一層函式包裝原先的裝飾器def timing(times): def outer(func): def wrapper(*args, **kwargs):
依據 @timing(2) 傳入 def timing(times): 來指定執行 func (works 函數) 的次數
for i in range(times): func(*args, **kwargs)