Python中多個(gè)裝飾器執(zhí)行順序驗(yàn)證深入講解
關(guān)于 Python 裝飾器執(zhí)行時(shí)的順序問(wèn)題,一直以來(lái)都保持粗略的理解概念:
- 裝飾器相當(dāng)于函數(shù)調(diào)用的語(yǔ)法糖,因此在函數(shù)執(zhí)行時(shí),會(huì)從最內(nèi)層括號(hào)開(kāi)始,逐層向外執(zhí)行。從代碼文本上看,就是距離被修飾函數(shù)越近的裝飾器,越先執(zhí)行
- 原始的裝飾器會(huì)覆蓋被修飾函數(shù)的
__name__等元數(shù)據(jù),需要使用functools.wraps修飾wrapper函數(shù),保留被修飾函數(shù)的元數(shù)據(jù) - Python 代碼存在編譯時(shí)與運(yùn)行時(shí)的區(qū)別,因此,裝飾器中代碼的執(zhí)行順序,不總是被裝飾函數(shù)最先執(zhí)行
最近遇到一些問(wèn)題,對(duì)以上概念的理解有些模糊,嘗試通過(guò)代碼驗(yàn)證。執(zhí)行 Python 版本為 3.9.12。
單模塊執(zhí)行
測(cè)試代碼
#!/usr/bin/python
# encoding: utf-8
def deco1(func):
print('Deco 1-1')
def wrapper(*args, **kwargs):
print('Deco 1 - wrapper 1')
r = func(*args, **kwargs)
print('Deco 1 - wrapper 2')
return r
print('Deco 1-2')
return wrapper
def deco2(func):
print('Deco 2-1')
def wrapper(*args, **kwargs):
print('Deco 2 - wrapper 1')
r = func(*args, **kwargs)
print('Deco 2 - wrapper 2')
return r
print('Deco 2-2')
return wrapper
def deco3(func):
print('Deco 3-1')
def wrapper(*args, **kwargs):
print('Deco 3 - wrapper 1')
r = func(*args, **kwargs)
print('Deco 3 - wrapper 2')
return r
print('Deco 3-2')
return wrapper
@deco3
@deco2
@deco1
def test():
print("Test func")
if __name__ == '__main__':
print('Test begin')
test()
print('Test end')
測(cè)試代碼中,deco1、deco2 和 deco3 裝飾器都是同步函數(shù),只有輸出日志是不同的,因此,可以認(rèn)為輸出日志的順序,即是代碼執(zhí)行的順序。
執(zhí)行結(jié)果
代碼執(zhí)行結(jié)果如下:
Deco 1-1 Deco 1-2 Deco 2-1 Deco 2-2 Deco 3-1 Deco 3-2 Test begin Deco 3 - wrapper 1 Deco 2 - wrapper 1 Deco 1 - wrapper 1 Test func Deco 1 - wrapper 2 Deco 2 - wrapper 2 Deco 3 - wrapper 2 Test end
執(zhí)行結(jié)果分析
分析執(zhí)行結(jié)果的順序可以發(fā)現(xiàn):
- 進(jìn)入模塊
__main__入口之前,會(huì)先“編譯”裝飾器代碼,因此 "Test begin" 之前會(huì)打印Deco 1-1等日志 - 在“Test begin”執(zhí)行后,
Deco 3 - wrapper 1->Deco 2 - wrapper 1->Deco 1 - wrapper 1依次出現(xiàn),與裝飾器從上到下的應(yīng)用順序相符,而“Test func”之后,Deco 1 - wrapper 2->Deco 2 - wrapper 2->Deco 3 - wrapper 2依次執(zhí)行,順序變成后進(jìn)先出 - 所有裝飾器代碼執(zhí)行結(jié)束,才輪到最后的“Test end”
多個(gè)裝飾器的“編譯”順序,符合“距離最近的先執(zhí)行”的印象
如果調(diào)整裝飾器的應(yīng)用順序,讓 deco2 最先修飾 test,關(guān)鍵代碼如下:
@deco3
@deco1
@deco2
def test():
print("Test func")
執(zhí)行時(shí)輸出結(jié)果如下,可以發(fā)現(xiàn)“Test begin”之前的部分中,deco2最先被調(diào)用
Deco 2-1 Deco 2-2 Deco 1-1 Deco 1-2 Deco 3-1 Deco 3-2 Test begin Deco 3 - wrapper 1 Deco 1 - wrapper 1 Deco 2 - wrapper 1 Test func Deco 2 - wrapper 2 Deco 1 - wrapper 2 Deco 3 - wrapper 2 Test end
結(jié)論 v1.0
分析上面代碼的執(zhí)行順序,可以得到以下結(jié)論:
- 執(zhí)行被裝飾的函數(shù)之前,存在類似“編譯”的階段,會(huì)執(zhí)行裝飾器中 wrapper 函數(shù)之外的代碼
- 多個(gè)裝飾器修飾同一個(gè)函數(shù)的情況下,最外層(即寫(xiě)在函數(shù)代碼塊最上面一行)的裝飾器的 wrapper 函數(shù)中,被裝飾函數(shù)之前的代碼最先被執(zhí)行,然后是次外層的裝飾器中被裝飾函數(shù)之前的代碼,依此類推,直到真正執(zhí)行被裝飾函數(shù);如果 wrapper 函數(shù)內(nèi)存在執(zhí)行后的代碼,則執(zhí)行順序與 wrapper 函數(shù)內(nèi)被裝飾函數(shù)執(zhí)行前代碼的執(zhí)行順序相反,可以認(rèn)為全部代碼的執(zhí)行順序符合
deco3(deco2(deco1(test())))的函數(shù)式執(zhí)行順序,與文檔說(shuō)明相符
多模塊執(zhí)行
測(cè)試裝飾器是否應(yīng)用編譯緩存。將裝飾器代碼與測(cè)試代碼放在不同的模塊中。
測(cè)試代碼
decos.py
#!/usr/bin/python
# encoding: utf-8
def deco1(func):
print('Deco 1-1')
def wrapper(*args, **kwargs):
print('Deco 1 - wrapper 1')
r = func(*args, **kwargs)
print('Deco 1 - wrapper 2')
return r
print('Deco 1-2')
return wrapper
def deco2(func):
print('Deco 2-1')
def wrapper(*args, **kwargs):
print('Deco 2 - wrapper 1')
r = func(*args, **kwargs)
print('Deco 2 - wrapper 2')
return r
print('Deco 2-2')
return wrapper
def deco3(func):
print('Deco 3-1')
def wrapper(*args, **kwargs):
print('Deco 3 - wrapper 1')
r = func(*args, **kwargs)
print('Deco 3 - wrapper 2')
return r
print('Deco 3-2')
return wrapper
decorator-test.py
#!/usr/bin/python
# encoding: utf-8
from decos import deco1, deco2, deco3
@deco2
def test2():
print("Test 2 func")
@deco3
@deco2
@deco1
def test():
print("Test func")
if __name__ == '__main__':
print('Test begin')
test2() # 連續(xù)執(zhí)行多個(gè)被裝飾函數(shù)
print("==" * 10)
test()
print('Test end')
執(zhí)行結(jié)果
Deco 2-1 Deco 2-2 Deco 1-1 Deco 1-2 Deco 2-1 Deco 2-2 Deco 3-1 Deco 3-2 Test begin Deco 2 - wrapper 1 Test 2 func Deco 2 - wrapper 2 ==================== Deco 3 - wrapper 1 Deco 2 - wrapper 1 Deco 1 - wrapper 1 Test func Deco 1 - wrapper 2 Deco 2 - wrapper 2 Deco 3 - wrapper 2 Test end
結(jié)果分析
分析以上執(zhí)行結(jié)果,可以發(fā)現(xiàn):
Deco 2-1出現(xiàn)了兩次,可以認(rèn)為多個(gè)被裝飾函數(shù)一起被執(zhí)行時(shí),裝飾器 wrapper 函數(shù)之外的代碼會(huì)被重復(fù)執(zhí)行,執(zhí)行順序符合代碼中被裝飾函數(shù)的聲明順序- wrapper 函數(shù)內(nèi),被裝飾函數(shù)前后的代碼,它們的執(zhí)行順序與單模塊的情況下相同
結(jié)論
Python 中的裝飾器相當(dāng)于語(yǔ)法糖,實(shí)際執(zhí)行時(shí),代碼執(zhí)行順序與多層函數(shù)包裹目標(biāo)函數(shù)的寫(xiě)法的執(zhí)行順序一致(即 deco3(deco2(deco1(test()))) )。
在簡(jiǎn)單裝飾器的場(chǎng)景下,例如裝飾器的 wrapper 函數(shù)中直接return func(**args, **kwargs)的寫(xiě)法,裝飾器函數(shù)本身、內(nèi)部 wrapper 函數(shù)中不包含額外邏輯的情況下,可以認(rèn)為被裝飾函數(shù)先執(zhí)行并返回結(jié)果,然后越靠近被裝飾函數(shù)的裝飾器越先返回。
如果裝飾器本身包含較復(fù)雜的邏輯,例如測(cè)試代碼中 wrapper 函數(shù)執(zhí)行func前后都存在其他邏輯的寫(xiě)法,則需要參考多層函數(shù)的執(zhí)行順序,wrapper 函數(shù)之外的代碼會(huì)先于被執(zhí)行代碼執(zhí)行,wrapper 函數(shù)中func前的代碼會(huì)按從最外層裝飾器到最內(nèi)層的順序執(zhí)行,func后的代碼則反過(guò)來(lái),最內(nèi)層裝飾器的先執(zhí)行,符合多層函數(shù)調(diào)用堆棧的抽象。
到此這篇關(guān)于Python中多個(gè)裝飾器執(zhí)行順序驗(yàn)證的文章就介紹到這了,更多相關(guān)Python多個(gè)裝飾器執(zhí)行順序驗(yàn)證內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python使用PyGame實(shí)現(xiàn)打磚塊游戲
打磚塊也是一個(gè)非常經(jīng)典的小游戲,玩法大致如下,用一個(gè)小車接一個(gè)小球,然后反射小球,使之打在磚塊上,當(dāng)小球碰到磚塊之后,則磚塊被消掉,邏輯十分清晰,本文將給大家介紹了python使用PyGame實(shí)現(xiàn)打磚塊游戲,文中有詳細(xì)的代碼示例供大家參考,需要的朋友可以參考下2023-12-12
對(duì)python實(shí)現(xiàn)合并兩個(gè)排序鏈表的方法詳解
今天小編就為大家分享一篇對(duì)python實(shí)現(xiàn)合并兩個(gè)排序鏈表的方法詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-01-01
python測(cè)試驅(qū)動(dòng)開(kāi)發(fā)實(shí)例
這篇文章主要介紹了python測(cè)試驅(qū)動(dòng)開(kāi)發(fā)實(shí)例,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2014-10-10
Python 使用@property對(duì)屬性進(jìn)行數(shù)據(jù)規(guī)范性校驗(yàn)的實(shí)現(xiàn)
本文主要介紹了Python 使用@property對(duì)屬性進(jìn)行數(shù)據(jù)規(guī)范性校驗(yàn)的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10
python3通過(guò)subprocess模塊調(diào)用腳本并和腳本交互的操作
這篇文章主要介紹了python3通過(guò)subprocess模塊調(diào)用腳本并和腳本交互的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12
python實(shí)現(xiàn)騰訊滑塊驗(yàn)證碼識(shí)別
這篇文章主要介紹了python如何實(shí)現(xiàn)騰訊滑塊驗(yàn)證碼識(shí)別,幫助大家更好的理解和學(xué)習(xí)使用python,感興趣的朋友可以了解下2021-04-04
Python常見(jiàn)加密模塊用法分析【MD5,sha,crypt模塊】
這篇文章主要介紹了Python常見(jiàn)加密模塊用法,結(jié)合實(shí)例形式較為詳細(xì)的分析了MD5,sha與crypt模塊加密的相關(guān)實(shí)現(xiàn)方法與操作技巧,需要的朋友可以參考下2017-05-05
Python實(shí)現(xiàn)暴力破解有密碼的zip文件的方法
這篇文章主要介紹了Python實(shí)現(xiàn)暴力破解有密碼的zip文件的方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03
Python快速生成隨機(jī)密碼超簡(jiǎn)單實(shí)現(xiàn)
這篇文章主要介紹了Python快速生成隨機(jī)密碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08

