pyhon學習有一段時間了,今天又碰到了Django的中間件,其實Django里面的中間件,就是用了多層的裝飾器,然后去了解了一下多層裝飾器,感覺有寫東西差不多快忘了,也可能前面沒學好。
現在重新記錄,學習下。
普通裝飾器
def warp(func): print('我是裝飾器,碰到需要裝飾的函數,一開始執行這里') def inner(*args, **kwargs): print('這里才是真正的裝飾開始!') res = func(*args, **kwargs) print('裝飾結束') return res print('我這里是外圍,先出去了,里面的等需要的時候執行') return inner @warp # 裝飾器符號 def demo(x, y): return x + y if __name__ == '__main__': demo(1, 2)
運行結果: 我是裝飾器,碰到需要裝飾的函數,一開始執行這里 我這里是外圍,先出去了,里面的等需要的時候執行 這里才是真正的裝飾開始! 裝飾結束
其實我前面多層裝飾卡住就是這里。
裝飾器的本質就是一個函數,當你寫好裝飾器的函數從上而下運行時,一旦遇到@的提醒,python應該就開始進入准備狀態尋找需要裝飾的函數,當遇到下面一個需要被裝飾的函數demo時,
整個過程我在debug過程中發現,其實python馬上跳轉到開始去執行了warp函數,這就是剛開始我們輸出前面兩句的原因,其實這個時候,程序根本沒有執行到demo(1,2)的地方。
在這個warp 程序的運行過程中,還發生一個命名轉換的過程,就是demo=warp(),其實就是demo等於warp執行返回的函數inner,所以最后面執行demo(1,2)的時候,裝飾器開始工作。
理解了這個,后面多層的裝飾器更加好理解了。
下面上一個最簡單的兩層裝飾器。
def deco1(func): print('func 1 in') def wrapper1(): print('wrap1 in') func() print('wrap1 out') print('func 1 out') return wrapper1 def deco2(func): print('func 2 in') def wrapper2(): print('wrap2 in') func() print('wrap2 out') print('func 2 out') return wrapper2 @deco1 @deco2 def foo(): print('foo') if __name__ == '__main__': foo()
執行結果為:
func 2 in
func 2 out
func 1 in
func 1 out
wrap1 in
wrap2 in
foo
wrap2 out
wrap1 out
其實一開我因為沒能理解裝飾器的原理在兩個地方卡住了,第一個就是為什么先出的是
func 2 in
func 2 out
func 1 in
func 1 out
第二個問題就是為什么foo函數執行了一次,第二問題,網上查到了資料,第一個問題,通過對裝飾的理解和debug的過程,也理解了。
通過介紹,整個程序是至上而下運行的,在運行過程到
@deco1
@deco2
def foo():
一遇到需要裝飾的函數def的時候,用要開始重新走裝飾器,在走的過程中,先走跟函數近的裝飾器deco2,所以輸出了
func 2 in
func 2 out
再走deco1,所以輸出了
func 1 in
func 1 out
兩個裝飾器函數執行后,其實也運行了變量名替換。foo=deco1()即deco1程序執行的返回值wapper1,重點是來了wappper1里面的func其實已經成為deco2()即deco2執行程序的返回值wapper2,wapper2里面的func才是真正需要被裝飾的函數foo。
所以在warp1 in后面執行func的時候,直接跳到warp2了。
所有在整個多層裝飾的執行中,裝飾器的執行是至上而下的,但在裝飾器的(可以說內部調試中)是從哪個靠近需要被裝飾的函數,哪個先執行,簡單來說就是至下而上的。
通過這樣的理解,其實無論多少層裝飾器,真正被裝飾的原始函數將在最下面的那個裝飾器執行,另外的只不過在一層接着一層的調用函數,其實也可以認為在一次次運行裝飾過程。
整個過程有點像壓棧跟彈棧
最后一個帶參數的裝飾器。
這個更加像一個內部返回來兩次函數的定義函數,一共需要三個def來寫這個函數。由於日常使用不過,我的理解可能也不是很充分。
def deco(params): # params 為需要傳入的參數 print('floor1') def inner(func): print('floor2') def warp(*args, **kwargs): print('floor3') print('裝飾開始') for i in range(params): func(*args, **kwargs) print('裝飾結束') print('out3') print('out2') return warp print('out1') return inner @deco(5) #這個就是生成一個函數warp指向demo def demo(): print('ok') if __name__ == '__main__': demo()
執行結果:
/Users/shijianzhong/Desktop/swiper/.venv/bin/python /Users/shijianzhong/Desktop/swiper/paramsfunc.py
floor1
out1
floor2
out2
floor3
裝飾開始
ok
ok
ok
ok
ok
裝飾結束
out3
Process finished with exit code 0
根據前面分析的,我廢話少了點,其實在調試階段,由於是帶參數的裝飾器,所以在這個三層def的情況,裝飾器前期調試就已經運行了兩層,且命名demo指向warp函數。
正常情況下,前面的兩種情況理解,這個帶參數的應該理解不難。
按照我的理解,可以先認為,在調試的過程中,裝飾器自己先運行了一遍把函數返回了回來,然后用重命名了一下,這個帶參數的,可以認為是裝飾器自己運行了兩遍,其中一遍是把參數寫入內存中,待最里面的也就是真正需要裝飾的函數使用。
今天就寫這些。裝飾器這下應該問題不大了,有空把閉包函數也寫一下。
