前面章節已經講解了裝飾器的基本概念及用法,本節將結合實際工作中的幾個例子,帶讀者加深對它的理解。
裝飾器用於身份認證
首先是最常見的身份認證的應用。這個很容易理解,舉個最常見的例子,大家登入微信,需要輸入使用者名稱密碼,然後點選確認,這樣伺服器端便會查詢你的使用者名稱是否存在、是否和密碼匹配等等。如果認證通過,就可以順利登入;反之,則提示你登入失敗。
再比如一些網站,你不登入也可以瀏覽內容,但如果你想要發佈文章或留言,在點選發布時,伺服器端便會查詢你是否登入。如果沒有登入,就不允許這項操作等等。
如下是一個實現身份認證的簡單範例:
import functools
def authenticate(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
request = args[0]
# 如果使用者處於登入狀態
if check_user_logged_in(request):
# 執行函數 post_comment()
return func(*args, **kwargs)
else:
raise Exception('Authentication failed')
return wrapper
@authenticate
def post_comment(request, ...)
...
注意,對於函數來說,它也有自己的一些屬性,例如 __name__ 屬性,程式碼中 @functools.wraps(func) 也是一個裝飾器,如果不使用它,則 post_comment.__name__ 的值為 wrapper。而使用它之後,則 post_comment.__name__ 的值依然為 post_comment。
上面這段程式碼中,定義了裝飾器 authenticate,函數 post_comment() 則表示發表使用者對某篇文章的評論,每次呼叫這個函數前,都會先檢查使用者是否處於登入狀態,如果是登入狀態,則允許這項操作;如果沒有登入,則不允許。
裝飾器用於紀錄檔記錄
紀錄檔記錄同樣是很常見的一個案例。在實際工作中,如果你懷疑某些函數的耗時過長,導致整個系統的延遲增加,想線上上測試某些函數的執行時間,那麼,裝飾器就是一種很常用的手段。
我們通常用下面的方法來表示:
import time
import functools
def log_execution_time(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
res = func(*args, **kwargs)
end = time.perf_counter()
print('{} took {} ms'.format(func.__name__, (end - start) * 1000))
return res
return wrapper
@log_execution_time
def calculate_similarity(items):
...
這裡,裝飾器 log_execution_time 記錄某個函數的執行時間,並返回其執行結果。如果你想計算任何函數的執行時間,在這個函數上方加上@log_execution_time即可。
裝飾器用於輸入合理性檢查
在大型公司的機器學習框架中,呼叫機器叢集進行模型訓練前,往往會用裝飾器對其輸入(往往是很長的 json 檔案)進行合理性檢查。這樣就可以大大避免輸入不正確對機器造成的巨大開銷。
它的寫法往往是下面的格式:
import functools
def validation_check(input):
@functools.wraps(func)
def wrapper(*args, **kwargs):
... # 檢查輸入是否合法
@validation_check
def neural_network_training(param1, param2, ...):
...
其實在工作中,很多情況下都會出現輸入不合理的現象。因為我們呼叫的訓練模型往往很複雜,輸入的檔案有成千上萬行,很多時候確實也很難發現。
試想一下,如果沒有輸入的合理性檢查,很容易出現“模型訓練了好幾個小時後,系統卻報錯說輸入的一個引數不對,成果付之一炬”的現象。這樣的“慘案”,大大減緩了開發效率,也對機器資源造成了巨大浪費。
快取裝飾器
關於快取裝飾器的用法,其實十分常見,這裡以 Python 內建的 LRU cache 為例來說明。
LRU cache,在 Python 中的表示形式是 @lru_cache。@lru_cache 會快取進程中的函數引數和結果,當快取滿了以後,會刪除最近最久未使用的資料。
正確使用快取裝飾器,往往能極大地提高程式執行效率。舉個例子,大型公司伺服器端的程式碼中往往存在很多關於裝置的檢查,比如使用的裝置是安卓還是 iPhone,版本號是多少。這其中的一個原因,就是一些新的功能,往往只在某些特定的手機系統或版本上才有(比如 Android v200+)。
這樣一來,我們通常使用快取裝飾器來包裹這些檢查函數,避免其被反復呼叫,進而提高程式執行效率,比如寫成下面這樣:
@lru_cache
def check(param1, param2, ...) # 檢查使用者裝置型別,版本號等等
...