學(xué)習(xí)Python已經(jīng)有一段時間了,陸續(xù)學(xué)了一些基礎(chǔ)部分,但是理解的不是很深刻,每過一段時間就會忘記,所以不得不寫一些博客進(jìn)行記錄,加深自己的理解。這兩個星期一直在研究裝飾器,開始覺得很簡單,但是只知其然,真要寫一個裝飾器卻又不知如何下手了,顯然并沒有真正理解裝飾器的實(shí)現(xiàn)過程。經(jīng)過兩個星期的學(xué)習(xí)終于覺得自己對裝飾器的理解達(dá)到了一定的深度,因此 必須記錄一下。那就開始吧!
為什么需要裝飾器:存在一種需求就是,有一個函數(shù),在多個地方使用或者提供給項(xiàng)目組的他人使用,現(xiàn)在需要為此函數(shù)增加一些功能,但是你不能修改函數(shù)的代碼,也不能重寫一個替代函數(shù),然后讓別人重新使用你的替代函數(shù),怎么辦?裝飾器就是干這個的。
裝飾器:本質(zhì)是一個函數(shù),可在不改變原函數(shù)代碼與調(diào)用方式的情況下,為函數(shù)增加功能。
解裝飾器的實(shí)現(xiàn)需要以下幾個概念:
1、函數(shù)名就是一個變量,使用def方式定義一個函數(shù)時,就是將函數(shù)名與函數(shù)體建立一個指向關(guān)系,函數(shù)名對應(yīng)的值就是函數(shù)體在內(nèi)存中的地址;既然函數(shù)名是一個變量,那它就具有變量的特性,可以被修改,注意不是函數(shù)名被修改,而是函數(shù)名對應(yīng)的值可以被修改
2、高階函數(shù):高階函數(shù)就是把函數(shù)當(dāng)做參數(shù)傳遞,因?yàn)楹瘮?shù)名也是變量,所以可把函數(shù)名當(dāng)做參數(shù)傳遞,又因?yàn)楹瘮?shù)名對應(yīng)的值是是函數(shù)體,因此可以通過傳入的函數(shù)名參數(shù)使用傳入的函數(shù)。
3、函數(shù)閉包:把函數(shù)名當(dāng)做一個值返回,成為函數(shù)閉包;一般在一個函數(shù)的再部在定義一個函數(shù),那么在外層函數(shù)的外面是不可以使用函數(shù)內(nèi)的局部函數(shù)的,但是利用閉包,把內(nèi)部函數(shù)以函數(shù)名作為返回值,那么就可以做到在函數(shù)外使用內(nèi)部函數(shù)。關(guān)于函數(shù)的閉包,將另寫一份博客。
下面詳細(xì)說明裝飾器的實(shí)現(xiàn)過程:
- import time
- def luke():
- time.sleep(2)
- print('in function luke')
現(xiàn)在要實(shí)現(xiàn)一個裝飾器,在執(zhí)行l(wèi)uke函數(shù)時計算其執(zhí)行時間
既然裝飾器要裝飾一個函數(shù),就必須有一個參數(shù),這個參數(shù)用來接收被裝飾的函數(shù)的函數(shù)名
第一步定義一個裝飾器函數(shù),此函數(shù)有一個參數(shù)func,func用于接收一個函數(shù)名
- def caculate_func_run_time(func):
- pass
第二部實(shí)現(xiàn)函數(shù)執(zhí)行的時間計算:
- def caculate_func_run_time(func):
- start_time = time.time()
- func()
- end_time = time.time()
- print('func run time is %s'%(end_time - start_time))
第三步,利用函數(shù)閉包,在裝飾器內(nèi)部定義一個函數(shù),將函數(shù)體包裝,然后返回裝飾器內(nèi)部的包裝函數(shù)warpper
- def caculate_func_run_time(func):
- def warpper():
- start_time = time.time()
- func()
- end_time = time.time()
- print('func run time is %s'%(end_time - start_time))
- return warpper # 注意此處返回的是函數(shù)名,沒有'()'
第四步,利用函數(shù)名即變量的特性,修改luke函數(shù)名對應(yīng)的值
luke = caculate_func_run_time(luke)
經(jīng)過此步之后,luke函數(shù)名不變,但是其值對應(yīng)的已不再是原始函數(shù)體,而是caculate_func_run_time函數(shù)內(nèi)部包裝后的返 回值即warpper函數(shù)名對應(yīng)的值。
luke = caculate_func_run_time(luke)
luke()
以上兩步調(diào)用執(zhí)行時會打印luke函數(shù)執(zhí)行時間,這樣雖然沒有改變luke原始函數(shù)代碼,但是改變了函數(shù)調(diào)用方式,即需要先一步執(zhí)行l(wèi)uke = caculate_func_run_time(luke),然后執(zhí)行l(wèi)uke(),才能達(dá)到需要的效果,怎么辦呢?
python的裝飾器(語法糖)就派上用處了,使用以下語法:
- @caculate_func_run_time # 此語法的含義是: luke = caculate_func_run_time(luke)
- def luke():
- time.sleep(2)
- print('in function luke')
所謂語法糖,我的理解就是,@符號后的函數(shù)名將下一行的函數(shù)名當(dāng)作一顆糖(參數(shù))吃掉(進(jìn)行執(zhí)行過程),然后再吐出(返回)一個函數(shù)名還給你,即相當(dāng)于執(zhí)行這一條語句:luke = caculate_func_run_time(luke)
函數(shù)caculate_func_run_time 已經(jīng)是一個裝飾器了,但是有一個問題,原始函數(shù)若存在參數(shù)或者返回值,現(xiàn)在的裝飾器并不能接收原始函數(shù)的參數(shù)或者返回原始函數(shù)的返回值,怎么辦呢?
我們需要修改裝飾器,使其可以接收原始函數(shù)的參數(shù),并返回原始函數(shù)的返回值
- def caculate_func_run_time(func):
- def warpper(*args, **kwargs):
- start_time = time.time()
- res = func(*args, **kwargs)
- end_time = time.time()
- print('func run time is %s'%(end_time - start_time))
- return res
- return warpper
- @caculate_func_run_time # 此語法的含義是: luke = caculate_func_run_time(luke)
- def luke(sleep_time):
- time.sleep(sleep_time)
- print('in function luke')
- return 1
- luke(3)
以上裝飾器用res 接收func的執(zhí)行結(jié)果,最后返回,這就做到了返回原始函數(shù)的返回值
下面需要理解的是誰接收的luke函數(shù)的參數(shù)呢?
是warpper函數(shù)接收luke函數(shù)參數(shù),為什么?
@caculate_func_run_time ,此語法的含義是: luke = caculate_func_run_time(luke),luke函數(shù)名變量的值被修改,那這個值是什么呢?
是caculate_func_run_time(luke)執(zhí)行后的返回值,即warpper
因此luke == warpper
luke(3)函數(shù)調(diào)用就是warpper(3)函數(shù)調(diào)用,所以就是warpper函數(shù)接收了luke函數(shù)的參數(shù)
至于在定義warpper函數(shù)使用(*args, **kwargs),是因?yàn)槭褂媒M參數(shù)與關(guān)鍵字參數(shù)結(jié)合,可以達(dá)到接收任意參數(shù)的效果,
因此以上裝飾器函數(shù)可以裝飾任意函數(shù),達(dá)到計算函數(shù)執(zhí)行時間的效果。
為什么(*args, **kwargs),組參數(shù)與關(guān)鍵字參數(shù)結(jié)合可達(dá)到接收任意參數(shù),請參見python學(xué)習(xí)之函篇的博客。
那么至此還有一個問題,裝飾器可不可以有參數(shù)呢?當(dāng)然可以有,但是怎么實(shí)現(xiàn)呢?
無參裝飾器只是兩層包裝,內(nèi)層使用閉包,要實(shí)現(xiàn)有參裝飾器需要再次使用閉包進(jìn)行第三層包裝,這樣內(nèi)部兩層就可以使用有參裝飾器的參數(shù)。
- def caculate_func_run_time(*args, **kwargs)
- # 在此函數(shù)內(nèi)部任意位置均可使用裝飾器傳入的參數(shù)(*args, **kwargs)
- print(*args, **kwargs)
- def out_warpper(func):
- def warpper(*args, **kwargs):
- start_time = time.time()
- res = func(*args, **kwargs)
- end_time = time.time()
- print('func run time is %s'%(end_time - start_time))
- return res
- return warpper
- return out_warpper
- @caculate_func_run_time('有參裝飾器')
- def luke(sleep_time):
- time.sleep(sleep_time)
- print('in function luke')
- return 1
@caculate_func_run_time('有參裝飾器') ——語法糖的執(zhí)行步驟是:
1、執(zhí)行:caculate_func_run_time('有參裝飾器'),返回一個out_warpper
2、執(zhí)行:out_warpper(luke),返回一個warpper
3、更新原始函數(shù):luke = warpper
至此就做到了有參裝飾器的實(shí)現(xiàn),三層的裝飾器已經(jīng)可以處理任意參數(shù)的裝飾器,因此也就不再需要第四層的閉包封裝。
那么小伙伴們還想不想了解一下更高級的多級裝飾,這與三層的裝飾器可不一樣哦??纯聪旅娴拇a吧!
小伙伴們覺得應(yīng)該怎樣輸出呢?
- def wrapper1(func):
- def inner():
- print('w1,before')
- func()
- print('w1,after')
- return inner
- def wrapper2(func):
- def inner():
- print('w2,before')
- func()
- print('w2,after')
- return inner
- @wrapper2
- @wrapper1
- def foo():
- print('foo')
- foo()
下面我們一步步分析吧:
1、當(dāng)python解釋器執(zhí)行@warpper2時,實(shí)質(zhì)是執(zhí)行foo = warpper2(@warpper1),要執(zhí)行此條調(diào)用就必須先執(zhí)行@warpper1,因?yàn)閣arpper2需要一個實(shí)參值
2、執(zhí)行@warpper1就是普通2層裝飾器,將foo函數(shù)包裝在warpper1 內(nèi),由inner返回,我們可以看做是warpper1_innner
3、執(zhí)行warpper2(warpper1_innner),warpper2實(shí)際包裝的是warpper1返回的inner函數(shù),由warpper2 的inner返回, warpper2內(nèi)的inner函數(shù)執(zhí)行func()時,實(shí)際執(zhí)行的是warpper1的innner函數(shù),返回的函數(shù)我們可以看做是 warpper2_inner,此時foo函數(shù)即為warpper2_inner
4、執(zhí)行foo()函數(shù),即要先執(zhí)行warpper2返回的inner函數(shù),首先輸出“w2,before”;然后執(zhí)行func();此函數(shù)是warpper2的 參數(shù)接收的warpper1的inner函數(shù)
5、執(zhí)行warpper2->inner的func函數(shù),即執(zhí)行warpper1的inner函數(shù),那么先輸出“w1,before”;接著執(zhí)行func函數(shù),此函數(shù)即是warpper1函數(shù)的參數(shù)接收的foo函數(shù),因此接著輸出“foo”,warpper1內(nèi)的inner函數(shù)內(nèi)的func函數(shù)執(zhí)行完畢后,再輸出“w1,after”,至此warpper1的inner函數(shù)執(zhí)行完畢返回,即warpper2內(nèi)的inner函數(shù)內(nèi)的func函數(shù)執(zhí)行完畢返回,接著輸出“w2,after”
因此輸出順序是:
- w2,before
- w1,before
- foo
- w1,after
- w2,after
朋友們,如果是三級裝飾呢,相信小伙伴們已經(jīng)沒有多大問題了。至此關(guān)于Python裝飾器總結(jié)完畢,有大神路過的時候,瞅一眼,有什么問題請批評指正!