日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > python >内容正文

python

python装饰器调用顺序_聊一聊Python装饰器的代码执行顺序

發(fā)布時(shí)間:2023/12/10 python 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 python装饰器调用顺序_聊一聊Python装饰器的代码执行顺序 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

為什么寫這篇文章?

起因是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)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。