裝飾器在Python中是一個強大的高級用法,並且在流行Python框架中變得越來越常見。經常會用到裝飾器來增強函數的行為(動態的給一個對象添加一些額外的職責),包括記錄日志,權限校驗,性能測試,數據封裝等。有了裝飾器,我們可以抽離出大量和函數功能本身無關的雷同代碼並繼續重用。
Python裝飾器有兩種:
- 函數裝飾器:管理函數調用和函數對象
- 類裝飾器:管理類實例和類自身
為什么使用裝飾器?
經常會遇到給函數或類增加新功能的場景,當然我們可以使用函數調用或者其它技術來實現,但是使用裝飾器意圖明確,最小化擴展代碼的冗余,使用@語法糖,相對優雅。
裝飾器的原理是什么?
我們先來看一個最簡單的裝飾器:
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)
對上述代碼進行解釋:
- time_it返回wrapper函數對象
- 使用time_it裝飾func_a函數
- 調用被裝飾的func_a函數會運行wrapper函數,func_a其實是wrapper的引用
原理:我們知道Python中一切皆對象,可以將函數作為其它函數的返回值。可以看到,裝飾器的本質是一個函數,返回一個函數對象,通過"@"語法糖在包裝函數中引入裝飾器。
裝飾器的一個關鍵特性是,在被裝飾的函數定義之后立即執行。
@wraps
上述裝飾器中用到的了@wraps(func),在創建裝飾器時,一定要記得為包裝函數添加functools庫中的@wraps裝飾器,以保證函數的元數據(包括函數名,函數注解等)不被丟失。
當我們需要訪問為被裝飾器修飾的原包裝函數時,可以使用@wraps的__wrapped__
屬性來訪問。
內置裝飾器
Python有三個內置裝飾器:@staticmathod、@classmethod和@property
- @staticmethod:類的靜態方法,跟成員方法的區別是沒有self參數,並且可以在類不進行實例化的情況下調用。
- @classmethod:跟成員方法的區別是接收的第一個參數不是self,而是cls(當前類的具體類型)
- @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。