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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

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

發布時間:2023/12/10 python 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 python装饰器调用顺序_聊一聊Python装饰器的代码执行顺序 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

為什么寫這篇文章?

起因是QQ群里邊有人提了一個問題:之前導入模塊只需要1~2秒,為什么現在變成需要2~3分鐘?

我的第一感覺是:是不是導入的模塊頂層代碼里邊,做了什么耗時的事情。隔了一天,他的問題解決了,下邊是按照他的代碼寫了一個類似的例子:

import time

def set_log(func):

def wrap(*args, **kwargs):

return func(*args, **kwargs)

time.sleep(4)

return wrap

@set_log

def demo():

pass

為什么導入這個模塊的時候,會運行time.sleep(4),明明沒有調用demo函數呀?這就要從Python裝飾器代碼的執行順序說起了。

簡單介紹下裝飾器

在正式開始之前,先簡單科普一下Python的裝飾器,裝飾器可以對已有的函數,添加額外的功能,甚至于完全改變函數的執行效果。舉個例子,現在想統計幾個函數的執行耗時,函數是這樣的:

import time

import random

def a_func():

time.sleep(random.randint(1, 5))

當然,我們可以這么寫

def a_func():

start_time = time.time()

time.sleep(random.randint(1, 5))

print("cost time: {}".format(time.time() - start_time))

這樣帶來的問題是代碼的可維護性不佳,尤其你有多個函數需要計算耗時的時候,萬一某天突然想去掉這些統計代碼呢~

所以像這種有切面需求的場景,裝飾器是一個非常漂亮的設計。

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))

只需要對統計耗時的函數掛上一個裝飾器,結果就自動出來,無需改動之前的代碼,非常方便。

Python也支持帶參數的裝飾器,比如剛剛的cost_time加入一個報警機制,如果函數執行耗時大于1秒,就發出警告。

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()

執行結果:

cost time: 3.0002505779266357

warning, cost time is 3.0002505779266357 !!!

Python裝飾器代碼的執行順序

回到我們的主題,首先把剛剛的例子加入一些打印:

import time

print("準備編寫裝飾器")

def set_log(func):

print("裝飾器頂層代碼")

def wrap(*args, **kwargs):

print("裝飾器內層代碼")

return func(*args, **kwargs)

# time.sleep(4)

print("準備返回wrap對象")

return wrap

print("準備編寫demo函數")

@set_log

def demo():

print("正在運行demo函數")

if __name__ == '__main__':

print("準備運行demo函數")

demo()

運行結果是:

準備編寫裝飾器

準備編寫demo函數

裝飾器頂層代碼

準備返回wrap對象

準備運行demo函數

裝飾器內層代碼

正在運行demo函數

所以在運行demo函數之前,已經做了:

準備編寫裝飾器

準備編寫demo函數

裝飾器頂層代碼

準備返回wrap對象

也就是說,就算你沒有運行demo函數,只是導入了這個模塊,上邊的這4件事情,都是會一一執行的。

是不是有點懵?

讓我們從頭開始,梳理一遍這個過程。

Python的代碼是從上往下依次執行的,所以當你導入這個模塊,第一句運行的代碼就是

import time

然后就來到了

print("準備編寫裝飾器")

接著是來到了set_log裝飾器函數的定義

def set_log(func):

需要注意的時候,在這里Python只運行了函數的定義語句,對于函數內部的執行,是直接跳過去的,并沒有運行。

繼續往下,來到了

print("準備編寫demo函數")

此時重點來了,到了demo函數的定義了

@set_log

def demo():

print("正在運行demo函數")

因為代碼從上往下依次運行的機制,Python解釋器首先到了@set_log這句代碼,@這個符號是Python提供的語法糖,它本質上是為了簡化了裝飾器的寫法,上邊的寫法等于

def demo():

print("正在運行demo函數")

demo = set_log(demo)

于是Python開始執行set_log裝飾器,來完成對demo函數的修飾。

def set_log(func):

print("裝飾器頂層代碼")

def wrap(*args, **kwargs):

print("裝飾器內層代碼")

return func(*args, **kwargs)

# time.sleep(4)

print("準備返回wrap對象")

return wrap

首先來到的是

print("裝飾器頂層代碼")

然后是裝飾器內部wrap函數的定義,同樣是,只運行了定義語句,跳過函數的內部執行代碼

def wrap(*args, **kwargs):

然后來到了打印“準備返回wrap對象”,以及返回wrap對象,要注意,在返回了wrap函數對象后,此時demo函數,其實已經被替換成了wrap函數對象。

print("準備返回wrap對象")

return wrap

完成了對demo函數的修飾后,代碼也來到了最后的調用demo函數的部分

if __name__ == '__main__':

print("準備運行demo函數")

demo()

新的重點來了~

上邊說到,在裝飾器內部返回了wrap對象后,demo已經被替換成了wrap函數對象了。

也就說說,運行 demo(),其實就是運行wrap()

def wrap(*args, **kwargs):

print("裝飾器內層代碼")

return func(*args, **kwargs)

所以代碼來到了wrap的函數內部,首先當然就是打印了“裝飾器內層代碼”。接下來是

return func(*args, **kwargs)

這里的func是不是很眼熟?我們回去看看set_log裝飾器的定義:

def set_log(func):

print("裝飾器頂層代碼")

def wrap(*args, **kwargs):

print("裝飾器內層代碼")

return func(*args, **kwargs)

# time.sleep(4)

print("準備返回wrap對象")

return wrap

func就是我們一開始傳給set_log裝飾器修飾的demo函數,還記得上邊寫的,裝飾器的兩種寫法嗎?

@set_log

def demo():

pass

# 等同于:

def demo():

pass

demo = set_log(demo)

于是代碼進入到了demo函數的內部去了~

def demo():

print("正在運行demo函數")

執行完畢,最終搞定,一個裝飾器的代碼執行順序就是這么走過來的。

最后,再來一個多重+帶參數的裝飾器的復雜一點的例子~

print("準備編寫裝飾器")

def set_log_first(func):

print("set_log_first裝飾器頂層代碼")

def wrap(*args, **kwargs):

print("set_log_first裝飾器內層代碼")

return func(*args, **kwargs)

print("set_log_first準備返回wrap對象")

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裝飾器內層代碼")

return func(*args, **kwargs)

print("set_log_second準備返回中間層的_wrap對象")

return _wrap

print("set_log_second準備返回頂層的wrap對象")

return wrap

print("準備編寫demo函數")

@set_log_first

@set_log_second()

def demo():

print("正在運行demo函數")

if __name__ == '__main__':

print("準備運行demo函數")

demo()

輸出是~

準備編寫裝飾器

準備編寫demo函數

set_log_second裝飾器頂層代碼

set_log_second準備返回頂層的wrap對象

set_log_second裝飾器中間層代碼

set_log_second準備返回中間層的_wrap對象

set_log_first裝飾器頂層代碼

set_log_first準備返回wrap對象

準備運行demo函數

set_log_first裝飾器內層代碼

set_log_second裝飾器內層代碼

正在運行demo函數

這里理解的重點就是,下邊的兩個寫法是等價的

@set_log_first

@set_log_second()

def demo():

print("正在運行demo函數")

# 等價于

demo = set_log_first(set_log_second()(demo))

裝飾器是不是很好玩呢?

總結

以上是生活随笔為你收集整理的python装饰器调用顺序_聊一聊Python装饰器的代码执行顺序的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。