1.定義
裝飾器模式是面向?qū)ο笳Z(yǔ)言中經(jīng)典的設(shè)計(jì)模式之一背传,它的出現(xiàn)是為了解決在多個(gè)函數(shù)中添加某一統(tǒng)一的功能,從而減少代碼的重用。例如常應(yīng)用的場(chǎng)景:插入日志,計(jì)算性能撩扒,緩存運(yùn)算結(jié)果,事務(wù)處理等吨些。這里就來(lái)了解python中的裝飾器搓谆。
2.實(shí)例分析
首先看個(gè)經(jīng)典的算法題:一個(gè)共有10個(gè)臺(tái)階的樓梯,從下面走到上面豪墅,一次只能邁1-3個(gè)臺(tái)階泉手,并且不能后退,走完這個(gè)樓梯有多少種走法偶器?
首先這個(gè)題目就是斐波那契數(shù)列的一個(gè)延伸斩萌,無(wú)非就是一個(gè)遞歸問(wèn)題,當(dāng)然,這里重點(diǎn)是裝飾器
這里我們來(lái)看看代碼:
def climbfloor(n,steps):
count=0
if n == 0:
count=1
elif n > 0:
for step in steps:
count+=climbfloor(n-step,steps)
return count
print(climbfloor(10,(1,2,3))
運(yùn)行后我們很快就能得出結(jié)果屏轰,但是如果是爬上100颊郎、200層樓得出來(lái)的運(yùn)算
結(jié)果就非常大了,那是不是每次都要重新計(jì)算霎苗?當(dāng)然姆吭,現(xiàn)在的機(jī)器計(jì)算速度的很快了,但是如果在多任務(wù)處理的情況下唁盏,每次重新計(jì)算就大大的浪費(fèi)了資源内狸,影響用戶的體驗(yàn)检眯。于是我們可以考慮將運(yùn)算結(jié)果加入緩存,這樣下次運(yùn)算就能直接使用已經(jīng)計(jì)算的結(jié)果昆淡,所以我們來(lái)定義另一個(gè)函數(shù)锰瘸,
def memo(func):
cache = {}
def wrap(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
return wrap
這個(gè)函數(shù)傳入一個(gè)func(函數(shù))參數(shù),加入一個(gè)包裹函數(shù)wrap()昂灵,在包裹函數(shù)里面添加緩存功能避凝,并調(diào)用func函數(shù)。這樣就達(dá)到了緩存計(jì)算結(jié)果的效果眨补,然后在打印結(jié)果前把需要進(jìn)行計(jì)算的函數(shù)傳入memo()函數(shù):
climbfloor = memo(climbfloor)
或者直接在原函數(shù)上面加入@memo關(guān)鍵字恕曲,這實(shí)際上是上面那句代碼的語(yǔ)法糖,二者是等價(jià)的渤涌。
這樣就把需要裝飾的函數(shù)當(dāng)作參數(shù)傳入了裝飾器函數(shù)佩谣,以后只要用到此類計(jì)算都可以直接傳入裝飾器,就可以為函數(shù)自動(dòng)寫入緩存功能实蓬。
3.定義帶參數(shù)的裝飾器
這里我們實(shí)現(xiàn)一個(gè)裝飾器茸俭,用來(lái)檢查被裝飾函數(shù)的參數(shù)類型,裝飾器可以通過(guò)參數(shù)指明函數(shù)參數(shù)的類型安皱,并且調(diào)用時(shí)如果檢測(cè)出類型不匹配就拋出一個(gè)異常调鬓,直接看截圖,重要注釋都標(biāo)明了酌伊。
這樣再隨便寫段測(cè)試代碼
@typeassert(int,str,list)
def f(a,b,c):
print(a,b,c)
f(1,'abc',[1,2,3])
f(1,2,[1,2,3])
點(diǎn)擊運(yùn)行
可以看到成功的進(jìn)行了參數(shù)類型的檢查 這樣函數(shù)帶參數(shù)的函數(shù)裝飾器就完成了
4.實(shí)現(xiàn)屬性可修改的函數(shù)裝飾器
現(xiàn)在需要用裝飾器計(jì)算一個(gè)函數(shù)的運(yùn)行時(shí)間腾窝,設(shè)置一個(gè)timeout值,如果函數(shù)運(yùn)行時(shí)間超過(guò)timeout居砖,就在控制臺(tái)打印出相關(guān)信息虹脯。這里我們來(lái)看看具體代碼
def warn(timeout):
def decorator(func):
def wrapper(*args,**kargs):
start = time.time()
# 傳入函數(shù)參數(shù)
res = func(*args,**kargs)
# 計(jì)算函數(shù)運(yùn)行所需的時(shí)間
used = time.time() - start
if used > timeout:
msg = '"%s": %s > %s'%(func.__name__,used,timeout)
# 打印msg信息
logging.warning(msg)
return res
return wrapper
return decorator
這樣就可以直接在對(duì)任意的函數(shù)使用了,現(xiàn)在有一個(gè)問(wèn)題奏候,如果希望在函數(shù)運(yùn)行時(shí)動(dòng)態(tài)改變timeout的值循集,應(yīng)該怎樣做?很簡(jiǎn)單蔗草,只要在裝飾器里面再定義一個(gè)函數(shù)setTimeout()
,就可以解決問(wèn)題了咒彤。但是可以看到,運(yùn)行時(shí)間的判斷是在wrapper()
中的咒精,是一個(gè)閉包镶柱,我們修改timeout的值應(yīng)該怎樣傳遞到閉包當(dāng)中?這里就需要用到nonlocal關(guān)鍵字模叙,用它來(lái)聲明變量歇拆,不是只在當(dāng)前函數(shù)中有效,而是能作用到整個(gè)裝飾器函數(shù)中。這樣就直接在decorator()
中直接定義一個(gè)setTimeout()函數(shù)
def setTimeout(k):
nonlocal timeout
timeout = k
wrapper.setTimeout = setTimeout
注意這個(gè)函數(shù)是放在wrapper返回之前的查吊,不然就起不到作用了。
最后進(jìn)行裝飾器的測(cè)試
@warn(1.5)
def test():
print("In sert")
while randint(0,1):
time.sleep(0.5)
for _ in range(30):
test()
test.setTimeout(1)
for _ in range(30):
test()
運(yùn)行后再觀察控制臺(tái)信息
這樣就實(shí)現(xiàn)了函數(shù)裝飾器屬性的修改湖蜕。
到這里逻卖,相信你應(yīng)該能更順手的使用Python的裝飾器了。
最后本文示例均來(lái)自慕課網(wǎng)實(shí)戰(zhàn)