淺析裝飾器
通常情況下,給一個物件新增新功能有三種方式:
- 直接給物件所屬的類新增方法;
- 使用組合;(在新類中建立原有類的物件,重複利用已有類的功能)
- 使用繼承;(可以使用現有類的,無需重複編寫原有類進行功能上的擴充套件)
一般情況下,優先使用組合,而不是繼承。但是裝飾器屬於第四種,動態的改變物件從而擴充套件物件的功能。
一般裝飾器的應用場景有列印日誌,效能測試,事務處理,許可權校驗;
Python 內建裝飾器的工作原理
理解Python裝飾器工作原理,首先需要理解閉包這一概念。閉包指的是一個函式巢狀一個函式,內部巢狀的函式呼叫外部函式的
變數,外部函式返回內嵌函式,這樣的結構就是閉包。
裝飾器就是閉包的一種應用,但是裝飾器引數傳遞的是函式。
簡單的閉包示例:
def add_num(x):
def sum_num(y):
return x+y
return sum_num
add_num5 = add_num(5)
total_num = add_num5(100)
print(total_num)
註解:
- add_num外函式接受引數x,返回內函式sum_num,而內函式sum_num接受引數y,將和add_num外函式接受引數x相加,返回結果。add_num5物件則是定義了add_num外函式接受的引數x為5,最後add_num5(100)返回的結果則是105。
裝飾器的基本使用
簡單計算函式執行時間裝飾器示例:
def times_use(func):
def count_times(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(end-start)
return result
return count_times
@times_use
def test_decorator():
time.sleep(2)
print("Test Decorator")
test_decorator()
註解:
- 這裡將函式test_decorator作為引數,傳入到times_use函式中,然後內部函式count_times則是會保留原有test_decorator函式程式碼邏輯,在執行test_decorator前儲存執行前時間,然後和執行後的時間進行比較,得出相應的耗時。
- 通過裝飾器的好處則是能在保留原有函式的基礎上,不用進行對原有函式的修改或者增加新的封裝,就能修飾函式增加新的功能。
根據日誌等級列印日誌裝飾器示例(帶引數的裝飾器):
def use_logging(level):
def decorator(func):
def wrapper(*args, **kwargs):
if level == "warn":
logging.warn("%s is running"% func.__name__)
result = func(*args, **kwargs)
print(result)
return result
return wrapper
return decorator
@use_logging("warn")
def test_decorator():
print("Test Decorator")
return "Success"
test_decorator()
計算函式執行時間的類裝飾器示例:
class logTime:
def __init__(self, use_log=False):
self._use_log = use_log
def __call__(self, func):
def _log(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
print(result)
end_time = time.time()
if self._use_log:
print(end_time-start_time)
return result
return _log
@logTime(True)
def test_decorator():
time.sleep(2)
print("Test Decorator")
return "Success"
functools wraps使用場景
使用裝飾器雖然能在儲存原有程式碼邏輯的基礎上擴充套件功能,但是原有函式中的元資訊會丟失,比如__name__, __doc__,引數列表。針對這種情況
可以使用functools.wraps,wraps也是一個裝飾器,但是會將原函式的元資訊拷貝到裝飾器函式中。
具體使用方法:
from functools import wraps
def use_logging(level):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
if level == "warn":
logging.warn("%s is running"% func.__name__)
result = func(*args, **kwargs)
print(result)
return result
return wrapper
return decorator
@use_logging("warn")
def test_decorator():
"""" Test Decorator DocString""""
time.sleep(2)
print("Test Decorator")
return "Success"
print(test_decorator.__name__)
print(test_decorator.__doc__)
註解:
- wraps裝飾器將傳入的test_decorator函式中的元資訊拷貝到wrapper這個裝飾器函式中,使得wrapper擁有和test_decorator的
元資訊。
關於裝飾器的執行順序
在日常業務中經常會使用多個裝飾器,比如許可權驗證,登入驗證,日誌記錄,效能檢測等等使用場景。所以在使用多個裝飾器
時,就會涉及到裝飾器執行順序的問題。先說結論,關於裝飾器執行順序,可以分為兩個階段:(被裝飾函式)定義階段、(被裝飾函式)執行階段。
- 函式定義階段,執行順序時從最靠近函式的裝飾器開始,從內向外的執行;
- 函式執行階段,執行順序時從外而內,一層層的執行;
多裝飾器示例:
def decorator_a(func):
print("Get in Decorator_a")
def inner_a(*args, **kwargs):
print("Get in Inner_a")
result = func(*args, **kwargs)
return result
return inner_a
def decorator_b(func):
print("Get in Decorator_b")
def inner_b(*args, **kwargs):
print("Get in Inner_b")
result = func(*args, **kwargs)
return result
return inner_b
@decorator_b
@decorator_a
def test_decorator():
"""test decorator DocString"""
print("Test Decorator")
return "Success"
執行結果:
Get in Decorator_a
Get in Decorator_b
Get in Inner_b
Get in Inner_a
Test Decorator
程式碼註解:
- 上述函式使用裝飾器可以相當於decorator_b(decorator_a(test_decorator()),即test_dcorator函式作為引數傳入到decorator_a函式中,然後列印"Get in Decorator_a",並且返回inner_a函式,給上層decorator_b函式,decorator_b函式接受了作為引數的inner_a函式,列印"Get in Decorator_b",然後返回inner_b函式;
- 此時test_decorator(),即呼叫了該inner_b函式,inner_b函式列印"Get in inner_b",然後呼叫inner_a函式,inner_a列印了"Get in Decorator_a",最後呼叫test_decorator函式。這樣從最外層看,就像直接呼叫了test_decorator函式一樣,但是可以在剛剛的過程中實現功能的擴充套件;
參考連結
https://www.zhihu.com/question/26930016
https://segmentfault.com/a/1190000007837364
https://blog.csdn.net/u013411246/article/details/80571462