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装饰器的代码执行顺序的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: @angular/platform-br
- 下一篇: JAVA 飞机大战