Python裝飾器

1. 什麼是裝飾器?python

  顧名思義,裝飾器就是在方法上方標一個帶有@符號的方法名,以此來對被裝飾的方法進行點綴改造。shell

  當你明白什麼是裝飾器以後,天然會以爲這個名字取得恰如其分,但做爲初學者來講多少仍是會有些迷茫。下面用代碼來講明怎麼理解裝飾器。函數

 1 #腳本1
 2 def target():
 3     print('this is target')
 4 
 5 def decorator(func):
 6     func()
 7     print('this is decorator')
 8 
 9 decorator(target)
10 -------------------------------------------
11 
12 運行結果爲:
13 
14 this is target
15 this is decorator

  Python容許將方法看成參數傳遞,所以以上腳本就是將target方法做爲參數傳入decorator方法中,這其實也是裝飾器的工做原理,以上代碼等同於:學習

 1 #腳本2
 2 def decorator(func):
 3     func()
 4     print('this is decorator')
 5 
 6 @decorator
 7 def target():
 8     print('this is target')
 9 
10 target
11 -------------------------------------------
12 運行結果:
13 
14 this is target
15 this is decorator

  所以能夠看出,所謂的裝飾器就是利用了Python的方法可做參數傳遞的特性,將方法target做爲參數傳遞到方法decorator中。ui

1 @decorator
2 def target():
3     ...

  這種在一個方法的上方加一個@符號的寫法,就是表示位於下方的方法將被做爲參數傳遞到位於@後面的decorator方法中。使用@符號只是讓腳本1中的代碼換了一個寫法,更加好看,固然也會更加靈活與實用,後面會講到這點。但它們的本質實際上是同樣的,這也就是裝飾器的工做原理。this

2. 裝飾器的原理spa

  若是你仔細看的話,會在腳本2中發現一個問題,那就是腳本2中最後一行的target只是一個方法名字,它不是正確的方法調用,正確寫法應該加上左右括號的target(),以下:.net

 1 #腳本3
 2 
 3 def decorator(func):
 4     func()
 5     print('this is decorator')
 6 
 7 @decorator
 8 def target():
 9     print('this is target')
10 
11 target()
12 --------------------------------------------
13 運行結果:
14 
15 this is target
16 this is decorator
17 Traceback (most recent call last):
18   File "C:/Users/Me/Desktop/ff.py", line 34, in <module>
19     target()
20 TypeError: 'NoneType' object is not callable

  正如你所看到的,若是按照正確的寫法,運行結果你會看到應該出現的兩行打印文字"this is target"和"this is decorator",還會出現錯誤提示,ff.py是我爲寫這篇心得臨時編寫的一個py腳本名字,提示說'NoneType'對象不可調用。這是怎麼回事?好吧,我如今必須告訴你,其實腳本2和腳本3中並非一個使用裝飾器的正確寫法,不是使用錯誤,而是做爲裝飾器的decorator方法寫的並不友好,是的,我不認爲它是錯誤的寫法,只是不友好。但只要你明白其中的道理,使用恰當的手段也是能夠運行正常的,這就是爲何腳本2看似寫錯了調用方法卻得出了正確的結果。固然學習仍是得規規矩矩,後面我會具體說正確的裝飾器怎麼書寫,在這裏我先解釋了一下腳本2和腳本3的運行原理,瞭解它們的運行原理和錯誤緣由,其實就是了解裝飾器的原理。設計

  腳本2和腳本3的區別在於target和target(),也就是說真正的差異在於()這個括號。當()被附加在方法或者類後面時,表示調用,或者稱爲運行及實例化,不管稱呼怎樣,本質意義沒有不一樣,都是調用給出的對象,當對象不具有調用性的時候,就會報錯:'某個類型' object is not callable。當一個方法被調用後,即target(),是否能被再次執行,取決於它是否會return一個對象,而且該對象能夠被調用。也許你會有點迷糊,對比一下代碼會比較容易理解我想表達的意思:rest

 1 >>>def returnme():
 2 >>>    print('this is returnme')
 3  
 4 >>>def target():
 5 >>>    print('this is target')
 6   
 7 >>>target
 8 <function target at 0x00000000030A40D0>
 9   
10 >>>target()
11 target
12 <function returnme at 0x00000000030A4268>
13  
14 >>>target()()
15 target
16 returnme
17 
18 >>>returnme()()
19 returnme
20 Traceback (most recent call last):
21   File "<pyshell#15>", line 1, in <module>
22     returnme()()
23 TypeError: 'NoneType' object is not callable

  如上所示,當直接在腳本中輸入target,它只是告訴編譯器(我想是編譯器吧,由於我也不是很懂所謂編譯器的部分),總之就是告訴那個不知道在哪一個角落控制着全部python代碼運行的「大腦」,在

  0x00000000030A40D0位置(這個位置應該是指內存位置)存有一個function(方法)叫target;在target後面加上(),表示調用該方法,即輸入target(),「大腦」便按照target方法所寫的代碼逐條執行,因而打印出了target字符串,而且「大腦」明白在0x00000000030A4268位置有一個叫returnme的方法;由於target對象調用後是會返回一個returnme方法,而且方法是能夠被調用的,所以你能夠直接這樣書寫target()(),「大腦」會逐條執行target中的代碼,而後return一個returnme,由於多加了一個(),表示要對返回的returnme進行調用,因而再次逐條執行returnme中的代碼,最後便能看到1五、16的打印結果;而returnme方法是沒有返回任何可調用的對象,所以當輸入returnme()()時,「大腦」會報錯。

  下面咱們能夠來解釋一下腳本2和腳本3的運行詳情,以前說過,裝飾器的工做原理就是腳本1代碼所演示的那樣。

 1 @decorator
 2 def target():
 3     ...
 4 
 5 等同於
 6 
 7 def decorator(target)():
 8     ...
 9 
10 注:python語法中以上寫法是非法的,以上只是爲了便於理解。

  當你調用被裝飾方法target時,其實首先被執行的是做爲裝飾器的decorator函數,而後「大腦」會把target方法做爲參數傳進去,因而:

 1 #腳本2
 2 def decorator(func):
 3     func()
 4     print('this is decorator')
 5   
 6 @decorator
 7 def target():
 8     print('this is target')
 9   
10 target
11 -------------------------------------------
12 
13 實際運行狀況:
14 首先調用decorator方法:decorator()
15 由於decorator方法含1個參數,所以將target傳入:decorator(target)
16 運行代碼「func()」,根據傳入的參數,實際執行target(),結果打印出:this is target
17 運行代碼"print('this is decorator')",結果打印出:this is decorator

  對比腳本3的運行狀況:

 1 #腳本3
 2 def decorator(func):
 3     func()
 4     print('this is decorator')
 5   
 6 @decorator
 7 def target():
 8     print('this is target')
 9   
10 target()
11 -------------------------------------------
12 
13 實際運行狀況:
14 首先調用decorator方法:decorator()
15 由於decorator方法含1個參數,所以將target傳入:decorator(target)
16 運行代碼「func()」,根據傳入的參數,實際執行target(),結果打印出:this is target
17 運行代碼"print('this is decorator')",結果打印出:this is decorator
18 
19 以上與腳本2中運行狀況徹底相同,接下來即是執行腳本2中target沒有的(),也就是執行調用命令。
20 因爲decorator(target)沒有返回一個能夠被調用的對象,所以「大腦」提示錯誤:'NoneType' object is not callable

  若是你還不是很清楚,請看下面的等價關係:

 1 @decorator
 2 def target():
 3     ...
 4 
 5 等同於
 6 def decorator(target)():
 7     ...
 8 
 9 所以:
10 target == decorator(target)
11 target() == decorator(target)()
12 
13 因此:
14 假設有一個變量var=target,在將target賦值給var時,實際上是將decorator(target)的調用結果賦值給var,由於var不具有調用性(not callable),所以執行var()時,編譯器會報錯它是個NoneType對象,不能調用。

  綜上所述,你大概已經可以明白所謂的裝飾器是怎麼一回事,它是怎麼工做的。但腳本2和腳本3中的寫法會帶來一些困惑,這個困惑就是經過咱們編寫的decorator裝飾器對target進行裝飾後,將target變成了一個永遠不能被調用的方法,或者說變成了一個調用就報錯的方法。這跟咱們的使用習慣以及對方法的認識是很不協調的,畢竟咱們仍是習慣一個方法天生註定能夠被調用這種見解。因此爲了知足咱們對方法的定義,咱們最好將做爲裝飾器的方法寫成一個能夠返回具備被調用能力的對象的方法。

 1 #腳本4
 2 def whatever():
 3     print('this is whatever')
 4 
 5 def decorator(func):
 6     func()
 7     print('this is decorator')
 8     return whatever  #1
 9 
10 @decorator
11 def target():
12     print('this is target')
13 
14 ------------------------------
15 輸入:target
16 結果:
17 this is target
18 this is decorator
19 
20 輸入:target()
21 結果:
22 this is target
23 this is decorator
24 this is whatever

  在#1的位置,你能夠return任何能夠被調用的方法或類,甚至你能夠直接寫成:

 1 def whatever():
 2     print('this is whatever')
 3 
 4 def decorator(func):
 5     return whatever
 6 
 7 @decorator
 8 def target():
 9     print('this is target')
10 
11 ------------------------------
12 輸入:target
13 結果:告訴編譯器在內存某個位置有一個叫whatever的方法
14 
15 輸入:target()
16 結果:this is whatever

  以上裝飾器的做用就是將target方法,完徹底全變成了whatever方法。但這隻能完美解釋裝飾器的功效,在實際當中則毫無心義,爲何要辛辛苦苦寫了一大堆代碼以後,最後的結果倒是完完整整地去調用另外一個早已存在的方法?若是要調用whatever方法,我爲何不在個人代碼中直接寫whatever呢?

  裝飾器,顧名思義,它就是要對被裝飾的對象進行一些修改,經過再包裝來達到不同的效果,儘管它能夠將被裝飾對象變得面目全非或者直接變成另外一個對象,但這不是它被髮明出來的主要意義,相反它被髮明出來是幫助咱們更高效更簡潔的編寫代碼,對其餘方法或類進行裝飾,而非摧毀它們。例如對接收數據的檢校,咱們只要寫好一個校驗方法,即可以在其餘許多方法前做爲裝飾器使用。

3. 常規的裝飾器

  從廣義上來講,裝飾器就是腳本1中,利用python能夠將方法傳參的特性,用一個方法去改變另外一個方法,只要改變成功,均可以認爲是合格的裝飾器。但這是理論上的合格,畢竟裝飾器是一種語法糖,應該是爲咱們帶來便利而不是無用功,因此:

 1 1. 將方法裝飾成不能被調用,很差:
 2 def decorator(func):
 3     func()
 4     print('this is decorator')
 5 
 6 2. 將原方法完全消滅,直接變成另外一個方法,很差:
 7 def decorator(func):
 8     return whatever
 9 
10 3. 保留原方法的同時再加上別的功能,很差:
11 def decorator(func):
12     func()
13     print('this is decorator')
14     return whatever

  在以上3種寫法中,前兩種明顯很差,簡直就是將裝飾器變成了惡魔。而第3種寫法,雖然看起來從新包裝了被修飾方法,但卻在方法調用前擅自執行了一些命令,即當你輸入target,而非target()時:

 1 #腳本4
 2 def whatever():
 3     print('this is whatever')
 4 
 5 def decorator(func):
 6     func()
 7     print('this is decorator')
 8     return whatever  #1
 9 
10 @decorator
11 def target():
12     print('this is target')
13 
14 ------------------------------
15 輸入:target
16 結果:
17 this is target
18 this is decorator

  你還沒有執行target(),編譯器卻已經打印了兩行字符串。這並非咱們想要的,當咱們在代碼中寫下target時,咱們是不但願編譯器當即執行什麼命令,咱們是但願編譯器在碰到target()時才執行運算。並且若是咱們並不但願返回whatever,咱們只想要經過裝飾器,使得target方法除了打印本身的"this is target",再多打印一行"this is decorator」,全部代碼只含有target和decorator兩個方法,無其餘方法介入,應該怎麼辦?

  當你這樣問的時候,其實就是已經開始瞭解裝飾器存在的意義了。如下即是爲解決這些問題的裝飾器的常規寫法:

 1 #腳本5
 2 
 3 def decorator(func):
 4     def restructure():    
 5         func()
 6         print('this is decorator')
 7     return restructure
 8 
 9 @decorator
10 def target():
11     print('this is target')

  是的,從最外層講,以上代碼其實只有兩個方法,decorator和target——裝飾和被裝飾方法。但在decorator內部內嵌了一個方法restructure,這個內嵌的方法纔是真正改造target的東西,而decorator其實只是負責將target傳入。這裏的restructure,至關於在腳本4中,被寫在decorator外面的whatever角色,用裝飾器的常規寫法也能夠寫出腳本4的效果,以下:

 1 def decorator(func):   
 2     func()
 3     print('this is decorator')
 4 
 5     def whatever():
 6         print('this is whatever')     
 7     return restructure
 8 
 9 @decorator
10 def target():
11     print('this is target')

  對比以上的寫法你會發現,python的方法傳參和類的繼承性質很類似,在decorator以內,whatever以外,你能夠寫入任何代碼,當執行target時,就開始初始化decorator,也就是執行decorator內部能夠執行的代碼;當執行target()時,會對初始化以後的decorator開始調用,所以這就要求decorator完成初始化以後必須返回一個具有調用性的對象。因此,除非你想要target方法初始化以前(其實是對decorator進行初始化)就執行一些代碼,不然不要在decorator和whatever中間插入代碼。

  正常狀況下,當decorator完成初始化,應該return一個可調用對象,也就是腳本5中的restructure方法,這個方法就是替代target的克隆人,在restructure中你能夠對target進行重寫,或其餘代碼來包裝target。所以你只是想初始化target的話(實際就是對restructure初始化),就應將你要初始化的代碼寫入到restructure內部去。另外你也能夠在decorator中內嵌多個方法,或多層方法,例如:

 1 #腳本6
 2 
 3 def decorator(func):
 4     def restructure():    
 5         func()
 6         print('this is decorator')
 7 
 8     def whatever():
 9         func()
10         print('this is whatever')
11 
12     return restructure
13 
14 @decorator
15 def target():
16     print('this is target')

  被decorator裝飾的target最後會多打印一行'this is decorator'仍是'this is whatever’,取決於decorator方法return的是它內部的哪個方法(restructure或whatever),所以以上代碼等價於如下寫法:

執行target()

等同於

首先初始化decorator(target),結果返回一個restructure,即:target == decorator(target) == restructure。

而後調用,即:target() == decorator(target)() == restructure()

與類同樣,當target被傳入decorator以後,做爲decorator內嵌方法是能夠調用(繼承)target方法的,這就是爲何restructure不用接受傳參就能夠改造target的緣由。

注:在python中,以上多個()()的寫法是非法的,這樣寫只是爲了便於理解。

  裝飾器這個概念原本也能夠設計成多個()這樣的形式,但這樣就破壞了python的基本寫法,並且很差看,尤爲當有多重裝飾的時候,所以使用@置於方法前的設計是更加優雅和清晰的。

  我之因此使用多個()這樣並不被python支持的寫法來闡述我對python的裝飾器的理解,是由於我相信經過這樣被python放棄的語法也能更好地幫助你理解python的繼承、傳參以及裝飾器,尤爲是帶有參數的裝飾器。

4. 裝飾帶有參數的方法

  首先請看如下代碼:

 1 def target(x):
 2     print('this is target %s'%x)
 3 
 4 def decorator(func,x):
 5     func(x)
 6     print('this is decorator %s'%x)
 7 
 8 decorator(target,'!')
 9 
10 等同於:
11 
12 def decorator(func):
13     def restructure(x):
14         func(x)
15         print('this is decorator %s'%x)
16     return restructure
17 
18 @decorator
19 def target(x):
20     print('this is target %s'%x)
21 
22 target('!')

  target(x)中的參數x是如何傳入,何時傳入裝飾器的?首先嚐試如下代碼:

 1 def decorator(func):
 2     print(x)  #增長一行代碼
 3     def restructure(x):
 4         func(x)
 5         print('this is decorator %s'%x)
 6     return restructure
 7 
 8 @decorator
 9 def target(x):
10     print('this is target %s'%x)
11 
12 target('!')

  此時編譯器會報錯參數x沒有被定義,也就是說在初始化decorator的時候,只有target方法被傳入,其參數x='!'並無傳入。

  如今讓咱們回顧一下以前說的裝飾器工做原理,以下:

1 target == decorator(target) == restructure。
2 
3 target() == decorator(target)() == restructure()
4 
5 同理:
6 
7 target(x) == decorator(target)(x) == restructure(x)

  因此,你能夠很清楚地瞭解到,做爲target的參數,其實不是傳給decorator,而是傳給初始化完decorator以後return的restructure。所以,若是裝飾器寫成以下代碼: 

1 def decorator(func):
2     def restructure():    #不帶參數的方法
3         func(x)
4         print('this is decorator %s'%x)
5     return restructure

  此時你輸入target('!'),編譯器會告訴你restructure沒有參數,但被傳入了1個參數,這個被傳入的參數就是x='!'。

  因此你如今明白了被裝飾的方法target(x),方法target和參數x是如何被傳入的,因此你必須保證初始化decorator以後返回的對象restructure方法形參與被裝飾的target方法形參相匹配,即:

1 若是定義爲:def target(x)
2 則裝飾器中:def restructure(x)
3 
4 若是定義爲:def target(x,y)
5 則裝飾器中:def restructure(x,y)

  你也許會發現若是想裝飾器同時裝飾target(x)和newtarget(x,y),以上寫法就沒法知足要求了。所以爲了讓裝飾器能夠適用於更多的對象,咱們最好讓裝飾器寫成以下形式:

 1 def decorator(func):
 2     def restructure(*x):
 3         func(*x)
 4         print('this is decorator')
 5     return restructure
 6 
 7 @decorator
 8 def target(x):
 9     print('this is target %s'%x)
10 
11 @decorator
12 def newtarget(x,y):
13     print('this is target %s%s'%(x,y))
14 
15 target('!')
16 newtarget('!','?')

  利用python的帶星號參數語法(*arg),你即可以傳入任意數量的參數,你也能夠設置帶雙星號的形參(**arg),即可以傳入字典形式的參數,單星形參和雙星形參能夠同時使用,如:def restructure(*arg, **arg)。

5. 帶有參數的裝飾器

  只要記住以上的裝飾器工做原理,你即可以知道如何寫出帶有參數的裝飾器,如:

 1 def newdecorator(i):
 2     def decorator(func):
 3         def restructure(x):
 4             func(x)
 5             print('this is decorator %s%s'%(i,x))
 6         return restructure
 7     return decorator
 8 
 9 @newdecorator('?')
10 def target(x):
11     print('this is target %s'%x)
12 
13 target('!')
14 -------------------------------------------------------
15 結果:
16 this is target !
17 this is decorator ?!

  以上代碼其實是:

1 target(x) == newdecorator(i)(target)(x) == decorator(target)(x) == reconstructure(x)

  同理,爲了知足不一樣數量的參數傳入,你也能夠將newdecorator(i)寫成newdecorator(*i, **ii)。

6. 結束語

  只要明白裝飾器的設計原理,即可以自如地寫出想要的裝飾器,哪怕是多重、多參數的裝飾器。

 

原文:http://blog.csdn.net/sxw3718401/article/details/39519587?utm_source=tuicool&utm_medium=referral

相關文章
相關標籤/搜索