粗淺聊聊Python裝飾器
淺析裝飾器
通常情況下,給一個對象添加新功能有三種方式:
- 直接給對象所屬的類添加方法;
- 使用組合;(在新類中創建原有類的對象,重複利用已有類的功能)
- 使用繼承;(可以使用現有類的,無需重複編寫原有類進行功能上的擴展)
一般情況下,優先使用組合,而不是繼承。但是裝飾器屬於第四種,動態的改變對象從而擴展對象的功能。
一般裝飾器的應用場景有列印日誌,性能測試,事務處理,許可權校驗;
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函數一樣,但是可以在剛剛的過程中實現功能的擴展;
參考鏈接
//www.zhihu.com/question/26930016
//segmentfault.com/a/1190000007837364
//blog.csdn.net/u013411246/article/details/80571462