裝飾器在Python中是一個強大的高級用法,而且在流行Python框架中變得愈來愈常見。常常會用到裝飾器來加強函數的行爲(動態的給一個對象添加一些額外的職責),包括記錄日誌,權限校驗,性能測試,數據封裝等。有了裝飾器,咱們能夠抽離出大量和函數功能自己無關的雷同代碼並繼續重用。python
Python裝飾器有兩種:git
爲何使用裝飾器?github
常常會遇到給函數或類增長新功能的場景,固然咱們可使用函數調用或者其它技術來實現,可是使用裝飾器意圖明確,最小化擴展代碼的冗餘,使用@語法糖,相對優雅。app
裝飾器的原理是什麼?框架
咱們先來看一個最簡單的裝飾器:函數
import time from functools import wraps def time_it(func): """ 輸出函數的運行時間 :param func: :return: """ @wraps(func) def wrapper(*args, **kwargs): start_time = time.time() result = func() end_time = time.time() process_time = end_time - start_time print(func.__name__, process_time) return result return wrapper @time_it def func_a(): time.sleep(2)
對上述代碼進行解釋:性能
原理:咱們知道Python中一切皆對象,能夠將函數做爲其它函數的返回值。能夠看到,裝飾器的本質是一個函數,返回一個函數對象,經過"@"語法糖在包裝函數中引入裝飾器。測試
裝飾器的一個關鍵特性是,在被裝飾的函數定義以後當即執行。日誌
@wrapscode
上述裝飾器中用到的了@wraps(func),在建立裝飾器時,必定要記得爲包裝函數添加functools庫中的@wraps裝飾器,以保證函數的元數據(包括函數名,函數註解等)不被丟失。
當咱們須要訪問爲被裝飾器修飾的原包裝函數時,可使用@wraps的__wrapped__
屬性來訪問。
內置裝飾器
Python有三個內置裝飾器:@staticmathod、@classmethod和@property
裝飾器嵌套
爲了支持多步驟的擴展,裝飾器語法容許咱們向一個裝飾的函數或方法添加多個裝飾器,若多個裝飾器同時裝飾一個函數,那麼裝飾器的調用順序和@語法糖的聲明順序相反,也就是:
@decorator1 @decorator2 def func(): pass
等效於:
func = decorator1(decorator2(func()))
裝飾器參數
函數裝飾器和類裝飾器都能接收參數,這些參數傳遞給了真正返回裝飾器的可調用對象,而裝飾器反過來又返回一個可調用對象。
裝飾器參數在裝飾發生以前就解析了,而且它們一般用來保持狀態信息供隨後的調用使用。
上述實例中,func_a()是沒有參數的,那若是添加參數的話,裝飾器該如何編寫以接收參數呢?能夠在裝飾器中使用*args和**kwargs代替參數:
def time_it(func): """ 輸出函數的運行時間 :param func: :return: """ @wraps(func) def wrapper(*args, **kwargs): start_time = time.time() result = func(*args, **kwargs) end_time = time.time() process_time = end_time - start_time print(func.__name__, process_time) return result return wrapper
帶參數的裝飾器
咱們有時候須要提供給被裝飾的函數特定的功能,須要在裝飾器中帶參數。好比在業務處理中咱們須要限定函數的執行超時時間,因爲每一個函數所對應的超時時間不同,因此須要在裝飾器中帶參數以實現。
裝飾器的語法容許咱們在調用時,提供其它參數,實現上述場景:
import time import signal import functools def func_timeout(timeout): """ 超時時間裝飾器 :param timeout: :return: """ def decorator(func): def handler(signum, frame): raise RuntimeError("run %s timeout !" % func.__name__) @functools.wraps(func) def wrapper(*args, **kwargs): signal.signal(signal.SIGALRM, handler) signal.alarm(timeout) func(*args, **kwargs) signal.alarm(0) return wrapper return decorator @func_timeout(timeout=10) def func(): time.sleep(11) print("#" * 100) if __name__ == '__main__': func()
類裝飾器
上述實例都是函數裝飾器,相比函數裝飾器,類裝飾器更加靈活,主要依靠類的__call__
方法,當使用@形式將裝飾器附加到函數上時,就會調用此方法。
舉個例子:
class Foo(object): def __init__(self, func): self._func = func def __call__(self, *args, **kwargs): print("class decorator start") self._func(*args, **kwargs) print("class decorator end") @Foo def func(): print("test123") if __name__ == '__main__': func()
以上,代碼見 my github。