裝飾器是Python中最難理解的語(yǔ)法之一静浴,但較之其他冷門(mén)語(yǔ)法又相對(duì)較常用堰氓。但有必要指出的是,這里所說(shuō)的較常用马绝,指的是Python自帶的一些裝飾器豆赏,如@property、@abstractmethod等富稻,自定義裝飾器在實(shí)際中是較少使用的掷邦。
1 高階函數(shù)
函數(shù),作為一段代碼的抽象椭赋,其本質(zhì)上也對(duì)應(yīng)于一個(gè)表示函數(shù)代碼起點(diǎn)的內(nèi)存地址抚岗。每當(dāng)調(diào)用一個(gè)函數(shù)時(shí),程序掛起并跳轉(zhuǎn)到函數(shù)所在的地址開(kāi)始執(zhí)行哪怔,結(jié)束后再退回到調(diào)用點(diǎn)宣蔚,繼續(xù)執(zhí)行接下來(lái)的語(yǔ)句。由此可見(jiàn)认境,就數(shù)據(jù)結(jié)構(gòu)而言胚委,函數(shù)與數(shù)組的原理類(lèi)似,可以用一個(gè)指針來(lái)保存函數(shù)的起始位置叉信。故在C語(yǔ)言中有“函數(shù)指針”類(lèi)型亩冬,且由于函數(shù)可以用指針來(lái)傳遞,函數(shù)指針就可以作為另一個(gè)函數(shù)的參數(shù)硼身,從而出現(xiàn)了“以函數(shù)作為參數(shù)的函數(shù)”硅急,這樣的函數(shù)就稱(chēng)為高階函數(shù)。
在Python中佳遂,由于對(duì)象引用的存在营袜,函數(shù)也同樣作為一類(lèi)對(duì)象(函數(shù)對(duì)象),可以通過(guò)其引用傳遞丑罪,故在Python中將函數(shù)作為參數(shù)傳遞荚板,與其他數(shù)據(jù)結(jié)構(gòu)的傳遞是一樣的凤壁。Python中就有一些常用的高階函數(shù),如sort啸驯、max客扎、min等帶有key參數(shù)的函數(shù),map罚斗、reduce、filter這樣的帶有函數(shù)參數(shù)的函數(shù)等等宅楞。這樣的函數(shù)都以函數(shù)作為參數(shù)针姿,并在函數(shù)內(nèi)部調(diào)用傳入的函數(shù)參數(shù)。
2 無(wú)參裝飾器
Python的裝飾器被定義為一個(gè)高階函數(shù)厌衙,這個(gè)高階函數(shù)接受一個(gè)函數(shù)作為其唯一函數(shù)距淫,并返回另一個(gè)函數(shù)作為其唯一返回值,這樣的特殊函數(shù)就稱(chēng)為裝飾器婶希。裝飾器函數(shù)內(nèi)部應(yīng)包含一個(gè)完整的函數(shù)定義過(guò)程榕暇,并將這個(gè)新的函數(shù)作為返回值返回。在此函數(shù)內(nèi)部應(yīng)包含原函數(shù)的調(diào)用語(yǔ)句喻杈,以及其他額外添加的語(yǔ)句彤枢。裝飾器定義完成后,就可以使用“@”符號(hào)對(duì)其他函數(shù)進(jìn)行裝飾筒饰。
當(dāng)使用裝飾器時(shí)缴啡,本質(zhì)上是進(jìn)行了如下過(guò)程:
@staticmethod
def xxx():
???pass
等同于:
def xxx():
???pass
xxx = staticmethod(xxx)
可見(jiàn),裝飾器語(yǔ)法等同于使用裝飾器函數(shù)處理原函數(shù)瓷们,并賦值回原函數(shù)的過(guò)程业栅。
對(duì)于裝飾器內(nèi)部定義的新函數(shù),其作為返回值返回后谬晕,應(yīng)保持與原函數(shù)一致的函數(shù)參數(shù)聲明碘裕,這樣才能保證參數(shù)正確傳遞。而裝飾器內(nèi)部定義的函數(shù)由于具有通用性攒钳,是不能像普通函數(shù)一樣定義有限多個(gè)形參的帮孔,故這個(gè)問(wèn)題的解決方案為:使用不定長(zhǎng)形參聲明,并在函數(shù)調(diào)用時(shí)使用參數(shù)解包夕玩。
下例定義了一個(gè)常見(jiàn)的計(jì)時(shí)用裝飾器:
import time
import numpy as np
def Timer(func):
???def newFunc(*args, **kwargs):
???????sTime = time.time()
???????returnTuple = func(*args, **kwargs)
???????eTime = time.time()
???????print('Time: %.6f' % (eTime - sTime))
???????return returnTuple
???return newFunc
@Timer
def Test(timesNum):
???for i in range(timesNum):
???????np.random.rand(100, 100).dot(np.random.rand(100, 100))
Test(10)
上述代碼定義了一個(gè)名為T(mén)imer的裝飾器函數(shù)你弦,這個(gè)函數(shù)接受一個(gè)函數(shù)作為參數(shù),并在內(nèi)部定義了一個(gè)聲明為def newFunc(*args, **kwargs)的通用函數(shù)燎孟,在這個(gè)函數(shù)內(nèi)部禽作,以解包參數(shù)*args, **kwargs調(diào)用了裝飾器傳入的函數(shù),并在調(diào)用前后保存了調(diào)用時(shí)間揩页。最終輸出消耗時(shí)間旷偿,并返回函數(shù)的返回值。上述這個(gè)函數(shù)定義,最終作為裝飾器函數(shù)的返回值返回萍程。在裝飾器外部幢妄,這個(gè)返回的函數(shù)將覆蓋原函數(shù)。所以茫负,在調(diào)用被Timer裝飾的Test函數(shù)時(shí)蕉鸳,函數(shù)不僅會(huì)執(zhí)行Test函數(shù)的內(nèi)容,還會(huì)執(zhí)行Timer裝飾器中所定義的附加內(nèi)容忍法,即雖然調(diào)用的是Test函數(shù)潮尝,但實(shí)際執(zhí)行的是以Test函數(shù)作為參數(shù)的newFunc函數(shù)七咧。
3 有參裝飾器
有參裝飾器是“返回裝飾器函數(shù)的函數(shù)”茄蚯,本人目前并不了解這種語(yǔ)法的具體應(yīng)用場(chǎng)景均蜜,故這里只對(duì)有參裝飾器做簡(jiǎn)單的語(yǔ)法上的討論糠悼。
仍然以上文中的計(jì)時(shí)函數(shù)為例闲勺,有參裝飾器可以使用參數(shù)修改裝飾器的行為誊役,如定義時(shí)間縮放:
import time
import numpy as np
def Timer(mulNum):
???def innerTimer(func):
???????def newFunc(*args, **kwargs):
???????????sTime = time.time()
???????????returnTuple = func(*args, **kwargs)
???????????eTime = time.time()
???????????print('Time: %.6f' % ((eTime - sTime) * mulNum))
???????????return returnTuple
???????return newFunc
???return innerTimer
@Timer(1000)
def Test(timesNum):
???for i in range(timesNum):
???????np.random.rand(100, 100).dot(np.random.rand(100, 100))
Test(10)
上述代碼中婿脸,Timer是一個(gè)有參裝飾器础倍,其參數(shù)用于定義時(shí)間縮放值咽弦。Timer應(yīng)返回一個(gè)裝飾器徒蟆,故整個(gè)裝飾器的定義被放在了Timer內(nèi)部。而innerTimer為裝飾器函數(shù)离唬,其最終被Timer返回后专。innerTimer應(yīng)接受一個(gè)函數(shù)作為參數(shù),并返回一個(gè)新的函數(shù)输莺,故在innerTimer內(nèi)部定義了一個(gè)新的函數(shù)作為返回值戚哎,這個(gè)函數(shù)的定義中包含了對(duì)傳入的函數(shù)參數(shù)func的調(diào)用,同時(shí)包含了計(jì)時(shí)語(yǔ)句嫂用,并最終利用時(shí)間型凳,以及有參裝飾器的參數(shù)mulNum共同計(jì)算輸出值。innerTimer裝飾器函數(shù)返回在其內(nèi)部定義的函數(shù)newFunc嘱函,而Timer裝飾器函數(shù)返回innerTimer裝飾器函數(shù)甘畅,并最終將此裝飾器交給被裝飾的原函數(shù)。
綜上往弓,有參裝飾器本質(zhì)上是一個(gè)“返回(無(wú)參)裝飾器函數(shù)的任意函數(shù)”疏唾,而無(wú)參裝飾器函數(shù)是一個(gè)“接受函數(shù)作為唯一參數(shù),并返回一個(gè)新的函數(shù)的函數(shù)”函似。有參裝飾器只要求返回值是一個(gè)無(wú)參裝飾器即可槐脏,而無(wú)參裝飾器函數(shù)將修飾被其裝飾的原函數(shù),從而將原函數(shù)覆蓋為一個(gè)裝飾后的新函數(shù)撇寞。從而使得后續(xù)代碼中所有對(duì)原函數(shù)的調(diào)用顿天,實(shí)際調(diào)用的都是修飾后的新函數(shù)堂氯。