Python · 裝飾器(Decorator)及其應用
- 萬物皆對象 —— Python
本章所介紹的裝飾器(Decorator)和以後會介紹的元類(Meta Class)都是上面這句話的具現,其中裝飾器告訴了我們「函數亦對象」,元類則會告訴我們「類亦對象」
所謂的「函數亦對象」 ,意味著函數可以被賦值給變數、通過變數也能調用該函數。舉個栗子:
def func(x): return x + 1plus_one = funcprint(plus_one(1))Out[1]:2
裝飾器的核心思想,就是裝飾函數這個對象、讓函數自身代碼不變的情況下、增添一些具有普適性的功能
典型且我們打算進行講解的一個栗子就是,計算一個函數的運行時間。我們希望做到對任意一個函數,只要我們調用我們的裝飾器、就能記錄它的耗時、從而可以進一步做性能分析與優化
為此,我們先來看裝飾器的基本用法:
@decoratordef func(*args, **kwargs): ...
它等價於:
def func(*args, **kwargs): ...func = decorator(func)
其中,decorator 以函數對象為輸入參數、並返回一個函數對象。一般來說,它的定義形如:
def decorator(func): def wrapper(*args, **kwargs): # Do something before function called ans = func(*args, **kwargs) # Do something after function called return ans return wrapper
(這裡用到了許多 Python 的知識;關於可變參數 *args 和 **kwargs 的說明可以看這裡,關於閉包(Closure)可以看這裡)
從而如果要實現對函數 func 的計時的話,只需再應用標準庫 time:
import timedef decorator(func): def wrapper(*args, **kwargs): t = time.time() ans = func(*args, **kwargs) t = time.time() - t return ans, t return wrapper
我們稍微測試一下這個裝飾器:
@decoratordef func(): for _ in range(10 ** 6): x = 0 return "Done"func()Out[2]:("Done", 0.02807450294494629)
Done!
當然,這個簡單的版本其實還有許多缺點,包括但不限於:
- 裝飾器本身無法傳入參數
- 函數的 __name__ 屬性發生了改變,導致有些依賴函數簽名的代碼執行會出錯
不過 Python 的強大之處就是,當你有一項功能覺得很棘手時、往往已經有現成的庫幫你解決了問題。在裝飾器這裡,wrapt 就是這麼一個庫。你可以通過:
pip install wrapt
來安裝它。其官方教程相當詳盡,這裡就只說說它的基本用法
上面我們定義的計時 decorator 在使用 wrapt 時會形如:
import timeimport wrapt@wrapt.decoratordef decorator(func, instance, args, kwargs): t = time.time() ans = func(*args, **kwargs) t = time.time() - t return ans, t
Done!可以看到少了一層嵌套、從而使代碼漂亮不少。同時它幾乎解決了所有裝飾器可能帶來的隱含問題,所以使用 wrapt 來定義裝飾器可能總會是一個更好的選擇
下面我們來說說怎麼給裝飾器傳參數。正如普通裝飾器是返回函數對象的函數,想給裝飾器傳參的話、就需要定義一個返回裝飾器的函數。仍然以 decorator 為例,這次我們打算用一個閾值來判斷函數 func 的運行快慢:
import timeimport wraptdef decorator(eps): @wrapt.decorator def wrapper(func, instance, args, kwargs): t = time.time() ans = func(*args, **kwargs) t = time.time() - t if t > eps: print("Slow!") else: print("Fast!") return ans, t return wrapper
我們測試一下我們新的 decorator:
@decorator(0.01)def func1(): for _ in range(10 ** 6): x = 0 return "Done"@decorator(0.05)def func2(): for _ in range(10 ** 6): x = 0 return "Done"func1()Slow!Out[3]: ("Done", 0.028105497360229492)func2()Fast!Out[4]: ("Done", 0.029077768325805664)
Done!
裝飾器還有許多值得挖掘、探討的地方,不過我打算到此為止、因為以上的功能通常來說已經相當足夠。最後放出我在我實現的神經網路裡面用到的裝飾器的效果來讓觀眾老爺們感受一下裝飾器的強大吧(代碼可以參見這裡):
推薦閱讀:
※Python 家族有多龐大
※Python數據分析及可視化實例之CentOS7.2+Python3x+Flask部署標準化配置流程
※Flask 實現小說網站 (二)
※Python實現3D建模工具
※Flask模板引擎:Jinja2語法介紹
TAG:Python |