標籤:
Python有許多出色的語言特性,裝飾器(Decorator)便是其中一朵奇葩。先來看看一段代碼:
def deco1(f): print ‘decorate 1‘ return fdef deco2(f): print ‘decorate 2‘ return f
@deco1
@deco2def foo(): return ‘hello‘
儲存並執行上面的代碼,你會看到如下輸出:
decorate 2decorate 1
函數foo沒有被調用,但是deco1,deco2被按照一個順序被調用了。deco1和deco2就是裝飾器。從上面的輸出可以看出兩點:
1 裝飾器是在代碼裝載時被調用的
2 調用的順是從下到上
現在來說說裝配器到底做了什麼。看代碼:
def deco1(f): print ‘decorate 1‘,f return fdef deco2(f): print ‘decorate 2‘,f return f@deco1@deco2def foo(): return ‘hello‘
這段代碼的執行結果為:
decorate 2 <function foo at 0x00000000021F79E8>decorate 1 <function foo at 0x00000000021F79E8>
裝飾器函數的參數‘f’被列印出來,而且就是我們的函數‘foo’,可見裝飾器的參數就是被裝飾的函數。真的是這樣嗎?再來看代碼:
def wraper(n,f): def foo1(): return ‘%s(%s)‘%(n,f())return foo1 def deco1(f): print ‘decorate 1‘,f return wraper(‘decorate 1‘,f)def deco2(f): print ‘decorate 2‘,f return wraper(‘decorate 2‘,f)@deco1@deco2def foo(): return ‘hello‘
這段代碼的執行結果為:
decorate 2 <function foo at 0x0000000002147A58>decorate 1 <function foo1 at 0x0000000002147AC8>
我覺得我現在可以總結一下了:
1 裝飾器在代碼裝載時被調用;
2 調用順序是從下到上的;
3 被裝飾函數‘foo’作為參數傳遞給第一個裝飾器‘deco2’,傳回值將作為參數傳遞給第二個裝飾器‘deco1’,然後依次向上直到最頂端的裝飾器;
4 最頂端的裝飾器的傳回值就是被裝飾以後的函數,我們暫時稱之為,也就是我們將來要執行的那個‘foo’。
下面,來我們說說裝飾後的函數,被調用會有什麼結果,看代碼:
def wraper(n,f): def foo1(): return ‘%s(%s)‘%(n,f()) return foo1def deco1(f): print ‘decorate 1‘,f return wraper(‘decorate 1‘,f)def deco2(f): print ‘decorate 2‘,f return wraper(‘decorate 2‘,f)@deco1@deco2def foo(): return ‘hello‘print foo()
這段代碼的執行結果為:
decorate 2 <function foo at 0x0000000002127A58>decorate 1 <function foo1 at 0x0000000002127AC8>decorate 1(decorate 2(hello))
不難看出,調用順序與裝飾順序剛好相反,最終函數也就是最頂端的裝飾器返回的函數最先被調用,然後依次調用到最初那個函數‘foo’。我在這裡故意規避了一個基本的事實,就是:其實最後被調用的就是頂端裝飾返回的那個函數,你必須手動在這個函數中調用前面的函數,見這裡:
return ‘%s(%s)‘%(n,f())
,才會產生調用連的效果。所以裝飾器並不會幫你完成所有的事情,他給了你充分的自由。說的這裡,你們要問了(如果你有足夠的好奇心):‘裝飾器能帶參數嗎’。答案是能,見下面的代碼:
def wraper(n,f): def foo1(): return ‘%s(%s)‘%(n,f()) return foo1def deco(n): def deco1(f): print n,f return wraper(n,f) return deco1@deco(‘decorate 1‘)@deco(‘decorate 2‘)def foo(): return ‘hello‘print foo()
這段代碼的執行結果為:
decorate 2 <function foo at 0x0000000002117AC8>decorate 1 <function foo1 at 0x0000000002117B38>decorate 1(decorate 2(hello))
哇,和之前的結果一模一樣,代碼還被簡化了不少。這是怎麼回事呢,我來簡單解釋下,函數‘deco’不是裝飾器,他只是一個返回裝飾器的函數,當你把它放到裝飾符號‘@‘後面時,python的文法起了一個美妙的作用,他會先調用這個函數,然後用傳回值值作為裝飾器。
大概就是這樣啦,文法大師也許會描述的更詳細更專業。源碼狂人還可能深入到python的C代碼裡尋找成因。不過,作為一個有理智的好青年我們就點到為止吧。
下次,我們再來掰扯一下,裝飾器都可以變出哪些戲法吧。洗洗睡了。
Python裝飾器(Decorator)簡介