python装饰器调用顺序_聊一聊Python装饰器的代码执行顺序
為什么寫這篇文章?
起因是QQ群里邊有人提了一個(gè)問(wèn)題:之前導(dǎo)入模塊只需要1~2秒,為什么現(xiàn)在變成需要2~3分鐘?
我的第一感覺(jué)是:是不是導(dǎo)入的模塊頂層代碼里邊,做了什么耗時(shí)的事情。隔了一天,他的問(wèn)題解決了,下邊是按照他的代碼寫了一個(gè)類似的例子:
import time
def set_log(func):
def wrap(*args, **kwargs):
return func(*args, **kwargs)
time.sleep(4)
return wrap
@set_log
def demo():
pass
為什么導(dǎo)入這個(gè)模塊的時(shí)候,會(huì)運(yùn)行time.sleep(4),明明沒(méi)有調(diào)用demo函數(shù)呀?這就要從Python裝飾器代碼的執(zhí)行順序說(shuō)起了。
簡(jiǎn)單介紹下裝飾器
在正式開(kāi)始之前,先簡(jiǎn)單科普一下Python的裝飾器,裝飾器可以對(duì)已有的函數(shù),添加額外的功能,甚至于完全改變函數(shù)的執(zhí)行效果。舉個(gè)例子,現(xiàn)在想統(tǒng)計(jì)幾個(gè)函數(shù)的執(zhí)行耗時(shí),函數(shù)是這樣的:
import time
import random
def a_func():
time.sleep(random.randint(1, 5))
當(dāng)然,我們可以這么寫
def a_func():
start_time = time.time()
time.sleep(random.randint(1, 5))
print("cost time: {}".format(time.time() - start_time))
這樣帶來(lái)的問(wèn)題是代碼的可維護(hù)性不佳,尤其你有多個(gè)函數(shù)需要計(jì)算耗時(shí)的時(shí)候,萬(wàn)一某天突然想去掉這些統(tǒng)計(jì)代碼呢~
所以像這種有切面需求的場(chǎng)景,裝飾器是一個(gè)非常漂亮的設(shè)計(jì)。
def cost_time(func):
def wrap(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
print("cost time: {}".format(time.time() - start_time))
return result
return wrap
@cost_time
def a_func():
time.sleep(random.randint(1, 5))
只需要對(duì)統(tǒng)計(jì)耗時(shí)的函數(shù)掛上一個(gè)裝飾器,結(jié)果就自動(dòng)出來(lái),無(wú)需改動(dòng)之前的代碼,非常方便。
Python也支持帶參數(shù)的裝飾器,比如剛剛的cost_time加入一個(gè)報(bào)警機(jī)制,如果函數(shù)執(zhí)行耗時(shí)大于1秒,就發(fā)出警告。
def cost_time(warn=1):
def wrap(func):
def _wrap(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
cost = time.time() - start_time
print("cost time: {}".format(cost))
if cost > warn:
print("warning, cost time is {} !!!".format(cost))
return result
return _wrap
return wrap
@cost_time()
def a_func():
time.sleep(random.randint(1, 5))
a_func()
執(zhí)行結(jié)果:
cost time: 3.0002505779266357
warning, cost time is 3.0002505779266357 !!!
Python裝飾器代碼的執(zhí)行順序
回到我們的主題,首先把剛剛的例子加入一些打印:
import time
print("準(zhǔn)備編寫裝飾器")
def set_log(func):
print("裝飾器頂層代碼")
def wrap(*args, **kwargs):
print("裝飾器內(nèi)層代碼")
return func(*args, **kwargs)
# time.sleep(4)
print("準(zhǔn)備返回wrap對(duì)象")
return wrap
print("準(zhǔn)備編寫demo函數(shù)")
@set_log
def demo():
print("正在運(yùn)行demo函數(shù)")
if __name__ == '__main__':
print("準(zhǔn)備運(yùn)行demo函數(shù)")
demo()
運(yùn)行結(jié)果是:
準(zhǔn)備編寫裝飾器
準(zhǔn)備編寫demo函數(shù)
裝飾器頂層代碼
準(zhǔn)備返回wrap對(duì)象
準(zhǔn)備運(yùn)行demo函數(shù)
裝飾器內(nèi)層代碼
正在運(yùn)行demo函數(shù)
所以在運(yùn)行demo函數(shù)之前,已經(jīng)做了:
準(zhǔn)備編寫裝飾器
準(zhǔn)備編寫demo函數(shù)
裝飾器頂層代碼
準(zhǔn)備返回wrap對(duì)象
也就是說(shuō),就算你沒(méi)有運(yùn)行demo函數(shù),只是導(dǎo)入了這個(gè)模塊,上邊的這4件事情,都是會(huì)一一執(zhí)行的。
是不是有點(diǎn)懵?
讓我們從頭開(kāi)始,梳理一遍這個(gè)過(guò)程。
Python的代碼是從上往下依次執(zhí)行的,所以當(dāng)你導(dǎo)入這個(gè)模塊,第一句運(yùn)行的代碼就是
import time
然后就來(lái)到了
print("準(zhǔn)備編寫裝飾器")
接著是來(lái)到了set_log裝飾器函數(shù)的定義
def set_log(func):
需要注意的時(shí)候,在這里Python只運(yùn)行了函數(shù)的定義語(yǔ)句,對(duì)于函數(shù)內(nèi)部的執(zhí)行,是直接跳過(guò)去的,并沒(méi)有運(yùn)行。
繼續(xù)往下,來(lái)到了
print("準(zhǔn)備編寫demo函數(shù)")
此時(shí)重點(diǎn)來(lái)了,到了demo函數(shù)的定義了
@set_log
def demo():
print("正在運(yùn)行demo函數(shù)")
因?yàn)榇a從上往下依次運(yùn)行的機(jī)制,Python解釋器首先到了@set_log這句代碼,@這個(gè)符號(hào)是Python提供的語(yǔ)法糖,它本質(zhì)上是為了簡(jiǎn)化了裝飾器的寫法,上邊的寫法等于
def demo():
print("正在運(yùn)行demo函數(shù)")
demo = set_log(demo)
于是Python開(kāi)始執(zhí)行set_log裝飾器,來(lái)完成對(duì)demo函數(shù)的修飾。
def set_log(func):
print("裝飾器頂層代碼")
def wrap(*args, **kwargs):
print("裝飾器內(nèi)層代碼")
return func(*args, **kwargs)
# time.sleep(4)
print("準(zhǔn)備返回wrap對(duì)象")
return wrap
首先來(lái)到的是
print("裝飾器頂層代碼")
然后是裝飾器內(nèi)部wrap函數(shù)的定義,同樣是,只運(yùn)行了定義語(yǔ)句,跳過(guò)函數(shù)的內(nèi)部執(zhí)行代碼
def wrap(*args, **kwargs):
然后來(lái)到了打印“準(zhǔn)備返回wrap對(duì)象”,以及返回wrap對(duì)象,要注意,在返回了wrap函數(shù)對(duì)象后,此時(shí)demo函數(shù),其實(shí)已經(jīng)被替換成了wrap函數(shù)對(duì)象。
print("準(zhǔn)備返回wrap對(duì)象")
return wrap
完成了對(duì)demo函數(shù)的修飾后,代碼也來(lái)到了最后的調(diào)用demo函數(shù)的部分
if __name__ == '__main__':
print("準(zhǔn)備運(yùn)行demo函數(shù)")
demo()
新的重點(diǎn)來(lái)了~
上邊說(shuō)到,在裝飾器內(nèi)部返回了wrap對(duì)象后,demo已經(jīng)被替換成了wrap函數(shù)對(duì)象了。
也就說(shuō)說(shuō),運(yùn)行 demo(),其實(shí)就是運(yùn)行wrap()
def wrap(*args, **kwargs):
print("裝飾器內(nèi)層代碼")
return func(*args, **kwargs)
所以代碼來(lái)到了wrap的函數(shù)內(nèi)部,首先當(dāng)然就是打印了“裝飾器內(nèi)層代碼”。接下來(lái)是
return func(*args, **kwargs)
這里的func是不是很眼熟?我們回去看看set_log裝飾器的定義:
def set_log(func):
print("裝飾器頂層代碼")
def wrap(*args, **kwargs):
print("裝飾器內(nèi)層代碼")
return func(*args, **kwargs)
# time.sleep(4)
print("準(zhǔn)備返回wrap對(duì)象")
return wrap
func就是我們一開(kāi)始傳給set_log裝飾器修飾的demo函數(shù),還記得上邊寫的,裝飾器的兩種寫法嗎?
@set_log
def demo():
pass
# 等同于:
def demo():
pass
demo = set_log(demo)
于是代碼進(jìn)入到了demo函數(shù)的內(nèi)部去了~
def demo():
print("正在運(yùn)行demo函數(shù)")
執(zhí)行完畢,最終搞定,一個(gè)裝飾器的代碼執(zhí)行順序就是這么走過(guò)來(lái)的。
最后,再來(lái)一個(gè)多重+帶參數(shù)的裝飾器的復(fù)雜一點(diǎn)的例子~
print("準(zhǔn)備編寫裝飾器")
def set_log_first(func):
print("set_log_first裝飾器頂層代碼")
def wrap(*args, **kwargs):
print("set_log_first裝飾器內(nèi)層代碼")
return func(*args, **kwargs)
print("set_log_first準(zhǔn)備返回wrap對(duì)象")
return wrap
def set_log_second(times=1):
print("set_log_second裝飾器頂層代碼")
def wrap(func):
print("set_log_second裝飾器中間層代碼")
def _wrap(*args, **kwargs):
print("set_log_second裝飾器內(nèi)層代碼")
return func(*args, **kwargs)
print("set_log_second準(zhǔn)備返回中間層的_wrap對(duì)象")
return _wrap
print("set_log_second準(zhǔn)備返回頂層的wrap對(duì)象")
return wrap
print("準(zhǔn)備編寫demo函數(shù)")
@set_log_first
@set_log_second()
def demo():
print("正在運(yùn)行demo函數(shù)")
if __name__ == '__main__':
print("準(zhǔn)備運(yùn)行demo函數(shù)")
demo()
輸出是~
準(zhǔn)備編寫裝飾器
準(zhǔn)備編寫demo函數(shù)
set_log_second裝飾器頂層代碼
set_log_second準(zhǔn)備返回頂層的wrap對(duì)象
set_log_second裝飾器中間層代碼
set_log_second準(zhǔn)備返回中間層的_wrap對(duì)象
set_log_first裝飾器頂層代碼
set_log_first準(zhǔn)備返回wrap對(duì)象
準(zhǔn)備運(yùn)行demo函數(shù)
set_log_first裝飾器內(nèi)層代碼
set_log_second裝飾器內(nèi)層代碼
正在運(yùn)行demo函數(shù)
這里理解的重點(diǎn)就是,下邊的兩個(gè)寫法是等價(jià)的
@set_log_first
@set_log_second()
def demo():
print("正在運(yùn)行demo函數(shù)")
# 等價(jià)于
demo = set_log_first(set_log_second()(demo))
裝飾器是不是很好玩呢?
總結(jié)
以上是生活随笔為你收集整理的python装饰器调用顺序_聊一聊Python装饰器的代码执行顺序的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: @angular/platform-br
- 下一篇: python抢货程序_Python自动化