學習裝飾器需理解如下預備知識:python
函數本質上也是一種變量,函數名即變量名,函數體就變量對應的值;函數體能夠做爲值賦給其餘變量(函數),也能夠經過函數名來直接調用函數。調用符號即()。segmentfault
函數內部能夠嵌套定義一層或多層函數,被嵌套的內部函數能夠在外層函數體內部調用,也能夠做爲返回值直接返回設計模式
在一個嵌套函數中,內部被嵌套的函數能夠調用外部函數非全局變量而且不受外部函數聲明週期的影響(便可以把外部函數非全局變量視爲全局變量直接調用)。閉包
把一個函數做爲參數傳遞給另一個函數,而且把函數名做爲返回值以便調用函數。app
python中變量定義的過程以下:函數
1. 在內存中分配一塊內存空間;性能
2. 將變量的值存放到這塊內存空間;學習
3. 將這塊內存空間的地址(門牌號)賦值給變量名(即變量名保存的是內存空間地址)測試
總結:變量保存的不是變量對應的真實值,而是真實值所被存放的內存空間地址,這也就意味着變量的調用須要經過調用內存空間地址(門牌號)來實現。將變量延伸到函數,函數和函數的參數都屬於變量,調用函數進行參數傳遞時,是對函數和參數兩個變量的同時調用,符合變量賦值及調用的機制(間接引用而非直接調用變量對應的值)。參數的傳遞實質上是一種引用傳遞,即經過傳遞對實參所在內存空間地址的指向來完成傳遞過程。ui
這一機制可經過id()來驗證:
1 list1 = ['Python', 'PHP', 'JAVA'] 2 list2 = list1 3 print(id(list1), '========', id(list2)) 4 print('') 5 6 7 def foo1(x): 8 print(id(foo1)) 9 print(id(x)) 10 print(foo1) 11 12 13 def foo2(): 14 pass 15 16 foo1(foo2()) 17 print('---------') 18 print(id(foo1)) 19 print(id(foo2())) 20 print(foo1) 21 22 輸出: 23 7087176 ======== 7087176 #普通變量調用,內存地址指向相同 24 25 7082192 26 1348178992 27 <function foo1 at 0x00000000006C10D0> 28 --------- # 函數調用先後,不只函數名指向的內存地址相同,實參和形參的內存地址也相同 29 7082192 30 1348178992 31 <function foo1 at 0x00000000006C10D0>
設想這樣一個現實場景:本身開發的應用在線上穩定運行了一年,後面隨着業務的發展發現原有的經過某些函數定義的部分功能須要擴展一下新功能,剛好現有的功能又做爲公共接口在不少地方被調用。
可能的實現方式:
1. 調整代碼,從新定義須要修改功能對應的函數
這須要傷筋動骨了,重點是須要確保代碼的一致性,另外有可能從新定義後原來的函數調用方式無法裝載新功能。要知道這是在線上穩定運行的系統呀!
2. 把新功能封裝成可接收函數做爲參數、同時調用原函數的高階函數,而後經過嵌套函數來調用返回高階函數
這就須要用到今天的主角裝飾器了。
顧名思義,裝飾器是用來裝飾的,它自己也是一個函數,只不過接收其它函數做爲參數並裝飾其它函數,爲其它函數提供額外的附加功能。最直接的定義是,裝飾器其實就是一個接收函數做爲參數,並返回一個替換函數的可執行函數(詳情參照下文論述)。
上文已經大概提到,裝飾器是裝飾其餘函數的,爲其餘函數提供本來沒有的附加功能。引用一段比較詳細的文字:裝飾器是一個很著名的設計模式,常常被用於有切面需求的場景,較爲經典的有插入日誌、性能測試、事務處理等。
裝飾器是解決這類問題的絕佳設計,有了裝飾器,咱們就可以抽離出大量函數中與函數功能自己無關的雷同代碼並繼續重用。歸納的講,裝飾器的做用就是爲已經存在的對象添加額外的功能。
定義和使用裝飾器需遵循如下原則:
先來逐個梳理如下要點:
1 import time 2 3 4 def timmer(func): #外層函數傳遞被裝飾的函數 5 def warpper(*args,**kwargs): 6 start_time=time.time() 7 func() #保持原樣調用被裝飾的函數 8 stop_time=time.time() 9 print('the func run time is %s' %(stop_time-start_time)) 10 return warpper #把內層實際調用執行被裝飾的函數以及封裝額外裝飾功能的函數名(內存空間地址)做爲返回值返回,以便後續調用執行 11 12 @timmer 13 def test1(): 14 time.sleep(3) 15 print('in the test1') 16 17 test1() 18 19 程序輸出: 20 in the test1 #原生方式調用執行被裝飾的函數 21 the func run time is 3.000171661376953 #附加了額外的程序執行時間統計功能
請注意這裏定義的warpper函數的參數形式,非固定參數意味着實際傳入的被裝飾的函數能夠有參數也能夠沒有參數。
如今咱們把上述雙層嵌套函數改裝成一個函數來試試:
1 import time 2 def timmer(func): 3 start_time=time.time() 4 func() 5 stop_time=time.time() 6 print('the func run time is %s' %(stop_time-start_time)) 7 return timmer #去掉內層嵌套函數warpper,直接返回timmer自身 8 9 10 @timmer 11 12 13 def test1(): 14 time.sleep(3) 15 print('in the test1') 16 test1() 17 18 程序輸出: 19 in the test1 20 Traceback (most recent call last): 21 the func run time is 3.000171661376953 22 File "E:/Python_Programs/S13/day4/deco.py", line 20, in <module> 23 test1() 24 TypeError: timmer() missing 1 required positional argument: 'func'
請注意咱們改編裝飾器後程序雖然能夠運行,但已然報錯了,提示最後一行在調用test1這個被裝飾的函數時少了一個位置參數func:改編後的程序的返回值
是timmer自己,而咱們在定義timmer函數時已經爲其定義了一個參數func,所以報出缺乏參數錯誤。
關於這個參數錯誤,咱們一樣能夠經過修改內層嵌套函數的參數形式來佐證一下:
1 import time 2 def timmer(func): 3 def warpper(x): #這裏故意爲內層函數定義一個參數 4 print(x) 5 start_time=time.time() 6 func() 7 stop_time=time.time() 8 print('the func run time is %s' %(stop_time-start_time)) 9 return warpper 10 11 @timmer 12 def test1(): 13 time.sleep(3) 14 print('in the test1') 15 16 test1() 17 18 程序輸出: 19 Traceback (most recent call last): 20 File "E:/Python_Programs/S13/day4/deco2.py", line 18, in <module> 21 test1() 22 TypeError: warpper() missing 1 required positional argument: 'x'
能夠看出咱們修改內層函數的參數定義後會報相同的錯誤,並且直接致使程序不能運行了!咱們第一個演示程序中內層函數的參數是非固定參數,無關緊要,所以運行OK。
還記得上文強調的裝飾器的原則麼?一是不改變對被裝飾函數的調用執行方式(就要原生態調用);二是不改變被裝飾函數的源代碼。這改編後的裝飾器的問題就在於不能知足第一條原生態調用被
裝飾函數的條件了。要修復這個問題,咱們只能返回一個不帶任何參數或者說能夠不帶參數的函數做爲返回值,而現狀是裝飾器函數自己已經被固化了,必須且只能傳入func一個參數以便將被裝飾
的函數傳遞給裝飾器,所以咱們不得不引入一個不帶參數的內嵌函數,用它來完成須要的裝飾並做爲返回值以便後續調用。
因而裝飾器就變成兩層嵌套函數,外層(第一層)函數負責把須要被裝飾的函數做爲參數傳遞到裝飾器內部,並定義整個裝飾器的返回值,內層(第二層)函數負責執行具體的裝飾功能,而外層定
義的返回值就是內層實際原生態調用被裝飾函數和執行額外裝飾功能的內層嵌套函數。兩層嵌套分工明確又相得益彰,仔細推敲下這設計模式真是太nb了!
這也是不少地方說高階函數+嵌套函數=>裝飾器的緣由。
在此也附上網上某大神的解答:
1 def deco1(func): #外層函數把被裝飾的函數做爲參數引入 2 def wrapper(): 3 print('Begin----') 4 func() #內嵌函數開始調用執行被裝飾的函數,調用方式是原生態的 5 print('End-----') 6 return wrapper # 此處返回的函數即爲替換函數,包含了對原函數調用執行和增長額外裝飾功能的邏輯,注意這裏返回的是函數名(門牌號) 7 8 9 @deco1 10 def test1(): 11 print('This is for test') 12 13 test1() #這裏的test1在執行時會被替換爲裝飾器中的wrapper函數,並不是本來意義上定義的test1函數了,本來意義上定義的test1函數對應於與wrapper中的func()
#這裏經過調用符號()來調用執行被替換後的test1函數,請注意裝飾器中的返回值是wrapper即替換函數的內存空間地址(門牌號),經過調用符號()便可獲取
函數體(對變量test1進行賦值處理) 14 15 程序輸出: 16 Begin---- 17 This is for test 18 End-----
1 __author__ = 'Beyondi' 2 #!/usr/bin/env python 3 #! -*- coding:utf-8 -*- 4 5 6 def dec2(func): #外層函數只能處理被裝飾的函數這一個參數 7 def wrapper(*args, **kwargs): #被裝飾的函數的參數,必定要在內嵌函數中引入處理 8 print('Begin to decorate...') 9 ret = func(*args, **kwargs) 10 print('Arguments are %s %s' % (args, kwargs)) 11 return ret 12 return wrapper 13 14 15 @dec2 16 def test2(x, y, z): 17 print('aaa') 18 return 2 19 20 test2('a', 'b', z='c') #實際調用時被裝飾的函數參數傳遞方式不變 21 22 程序輸出: 23 Begin to decorate... 24 aaa 25 Arguments are ('a', 'b') {'z': 'c'} 26
1 def deco(limit): #裝飾器自帶的參數須要再定義一個外部函數來引入 2 def dec2(func): #接收處理被裝飾的函數變量 3 def wrapper(*args, **kwargs): #處理被裝飾的函數傳遞的參數,邏輯不變 4 print('Begin to decorate...') 5 # print(args) 6 func(*args, **kwargs) 7 if len(args) >= limit: #裝飾器自帶的參數開始派上用場 8 print('Arguments OK') 9 else: 10 print('Arguemts error') 11 return wrapper 12 return dec2 13 14 15 @deco(2) #經過語法糖進行裝飾時,須要把裝飾器自帶的參數傳遞進去,改變這個實參會影響程序最後的輸出結果 16 def test2(x, y, z): 17 print('aaa') 18 return 2 19 20 test2('a', 'b', z='c') 21 22 程序輸出: 23 Begin to decorate... 24 aaa 25 Arguments OK # 程序輸出結果符合預期
以上程序代表,給裝飾器自己引入參數可實現更靈活強大的裝飾效果。須要注意的是裝飾器本身的參數必定要在裝飾器的最外層定義引入,此時真正的裝飾器 就是最裏層嵌套的函數了。這也是爲何講裝飾器至少是須要雙層嵌套的高階函數。