前言
python裝飾器本質上就是一個函數,它可以讓其他函數在不需要做任何代碼變動的前提下增加額外的功能,裝飾器的返回值也是一個函數對象。
很多python初學者學到面向對象類和方法是一道大坎,那么python中的裝飾器是你進入Python高級語法大門的一道坎。
計算函數運行時間
假設你寫了幾個函數,有一天領導心血來潮說,你把每個函數的運行時長(結束時間-開始時間)統計下,作為一個python實習生的你可能會這樣寫
原始函數
import time
def func_a():
print("hello")
time.sleep(0.5)
def func_b():
print("world")
time.sleep(0.8)
if __name__ == '__main__':
func_a()
func_b()
添加運行時長
作為一個實習生的你,可能想到的解決辦法如下
import time
def func_a():
start = time.time()
print("hello")
time.sleep(0.5)
end = time.time()
print("運行時長:%.4f 秒" % (end-start))
def func_b():
start = time.time()
print("world")
time.sleep(0.8)
end = time.time()
print("運行時長:%.4f 秒" % (end-start))
if __name__ == '__main__':
func_a()
func_b()
運行結果:
hello
運行時長:0.5009 秒
world
運行時長:0.8008 秒
上面的代碼雖然滿足了領導的要求,但是如果你寫的函數很多的話,每個函數都這樣去添加,會顯得代碼很臃腫,有很多重復代碼。
有一天你邊上的一個python老司機看了下你的代碼,給你指了條路:裝飾器
函數裝飾器
裝飾器可以寫成函數式裝飾器,也可以寫成一個類裝飾器,先從簡單的函數裝飾器開始學習。
python裝飾器本質上就是一個函數,它可以讓其他函數在不需要做任何代碼變動的前提下增加額外的功能,裝飾器的返回值也是一個函數對象。
runtime函數就是一個裝飾器了,它對原函數做了包裝並返回了另外一個函數,額外添加了一些功能。在函數上方使用@語法糖就可以調用這個裝飾器了
import time
def runtime(func):
def wrapper():
start = time.time()
f = func() # 原函數
end = time.time()
print("運行時長:%.4f 秒" % (end-start))
return f
return wrapper
@runtime
def func_a():
print("hello")
time.sleep(0.5)
@runtime
def func_b():
print("world")
time.sleep(0.8)
if __name__ == '__main__':
func_a()
func_b()
運行結果
hello
運行時長:0.5001 秒
world
運行時長:0.8001 秒
函數帶參數裝飾器
上面的runtime就是一個簡單的裝飾器模型了,但並不強壯,如果函數里面帶有參數,那就不管用了,並且函數的參數是不固定的,這時候就需要用到*args
,**kwargs
兩兄弟了
import time
def runtime(func):
def wrapper(*args, **kwargs):
start = time.time()
f = func(*args, **kwargs) # 原函數
end = time.time()
print("運行時長:%.4f 秒" % (end-start))
return f
return wrapper
@runtime
def func_a(a):
print("hello"+a)
time.sleep(0.5)
@runtime
def func_b(b, c="xx"):
print("world"+b+c)
time.sleep(0.8)
if __name__ == '__main__':
func_a("a")
func_b("b", c="xxx")
類裝飾器
關於__call__
方法,不得不先提到一個概念,就是可調用對象(callable),我們平時自定義的函數、內置函數和類都屬於可調用對象,
但凡是可以把一對括號()應用到某個對象身上都可稱之為可調用對象,判斷對象是否為可調用對象可以用函數 callable。
如果在類中實現了__call__
方法,那么實例對象也將成為一個可調用對象
import time
class runtime(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
start = time.time()
f = self.func(*args, **kwargs) # 原函數
end = time.time()
print("運行時長:%.4f 秒" % (end-start))
return f
@runtime
def func_a(a):
print("hello"+a)
time.sleep(0.5)
@runtime
def func_b(b, c="xx"):
print("world"+b+c)
time.sleep(0.8)
if __name__ == '__main__':
func_a("a")
func_b("b", c="xxx")
裝飾器帶參數
快到年底了,領導說運行的速度先不要太快了,讓客戶先加錢,然后再以正常的速度顯示,那么現在的需求是讓每個函數的運行時間加50%,該如何實現呢?
這就到了裝飾器的高級語法,裝飾器也需要帶上參數了
函數裝飾器
import time
def runtime(slowly=1):
def wrapper(func):
def inner_wrapper(*args, **kwargs):
start = time.time()
f = func(*args, **kwargs) # 原函數
end = time.time()
t = end-start
time.sleep((slowly-1)*t) # 延遲效果
new_end = time.time()
print("運行時長:%.4f 秒" % (new_end-start))
return f
return inner_wrapper
return wrapper
@runtime(1.5)
def func_a(a):
print("hello"+a)
time.sleep(0.5)
@runtime(1.5)
def func_b(b, c="xx"):
print("world"+b+c)
time.sleep(0.8)
if __name__ == '__main__':
func_a("a")
func_b("b", c="xxx")
類裝飾器
import time
class runtime(object):
def __init__(self, slowly=1):
self.slowly = slowly
def __call__(self, func):
def wrapper(*args, **kwargs):
start = time.time()
f = func(*args, **kwargs) # 原函數
end = time.time()
t = end-start
time.sleep((self.slowly-1)*t) # 延遲效果
new_end = time.time()
print("運行時長:%.4f 秒" % (new_end-start))
return f
return wrapper
@runtime(1.5)
def func_a(a):
print("hello"+a)
time.sleep(0.5)
@runtime(1.5)
def func_b(b, c="xx"):
print("world"+b+c)
time.sleep(0.8)
if __name__ == '__main__':
func_a("a")
func_b("b", c="xxx")
使用場景
用哪些地方需要使用裝飾器呢?
- 如果你用過locust,設置權重會用到
@task(1)
, - 如果你用過pytest框架,使用fixture功能的時候經常會用到
@pytest.fixture(scope="function")
- allure里面可以添加測試步驟
@allure.step('修改購物車')
- 被大量使用於Flask和Django web框架中,檢查是否被授權去使用一個web應用的端點(endpoint)。如
@login_required
- 也可以自己寫個裝飾器添加日志
python自動化交流 QQ群:779429633