天天看點

Python中的裝飾器用法詳解

這篇文章主要介紹了python中的裝飾器用法,以執行個體形式詳細的分析了python中的裝飾器的使用技巧及相關注意事項,具有一定參考借鑒價值,需要的朋友可以參考下

本文執行個體講述了python中的裝飾器用法。分享給大家供大家參考。具體分析如下:

這裡還是先由stackoverflow上面的一個問題引起吧,如果使用如下的代碼:

@makebold

@makeitalic

def say():

   return "hello"

列印出如下的輸出:

<b><i>hello<i></b>

你會怎麼做?最後給出的答案是:

def makebold(fn):

    def wrapped():

        return "<b>" + fn() + "</b>"

    return wrapped

def makeitalic(fn):

        return "<i>" + fn() + "</i>"

def hello():

    return "hello world"

print hello() ## 傳回 <b><i>hello world</i></b>

現在我們來看看如何從一些最基礎的方式來了解python的裝飾器。

裝飾器是一個很著名的設計模式,經常被用于有切面需求的場景,較為經典的有插入日志、性能測試、事務處理等。裝飾器是解決這類問題的絕佳設計,有了裝飾器,我們就可以抽離出大量函數中與函數功能本身無關的雷同代碼并繼續重用。概括的講,裝飾器的作用就是為已經存在的對象添加額外的功能。

1.1. 需求是怎麼來的?

裝飾器的定義很是抽象,我們來看一個小例子。

def foo():

    print 'in foo()'

foo()

這是一個很無聊的函數沒錯。但是突然有一個更無聊的人,我們稱呼他為b君,說我想看看執行這個函數用了多長時間,好吧,那麼我們可以這樣做:

import time

    start = time.clock()

    end = time.clock()

    print 'used:', end - start

很好,功能看起來無懈可擊。可是蛋疼的b君此刻突然不想看這個函數了,他對另一個叫foo2的函數産生了更濃厚的興趣。

怎麼辦呢?如果把以上新增加的代碼複制到foo2裡,這就犯了大忌了~複制什麼的難道不是最讨厭了麼!而且,如果b君繼續看了其他的函數呢?

1.2. 以不變應萬變,是變也

還記得嗎,函數在python中是一等公民,那麼我們可以考慮重新定義一個函數timeit,将foo的引用傳遞給他,然後在timeit中調用foo并進行計時,這樣,我們就達到了不改動foo定義的目的,而且,不論b君看了多少個函數,我們都不用去修改函數定義了!

 def foo():

def timeit(func):

    func()

    end =time.clock()

timeit(foo)

看起來邏輯上并沒有問題,一切都很美好并且運作正常!……等等,我們似乎修改了調用部分的代碼。原本我們是這樣調用的:foo(),修改以後變成了:timeit(foo)。這樣的話,如果foo在n處都被調用了,你就不得不去修改這n處的代碼。或者更極端的,考慮其中某處調用的代碼無法修改這個情況,比如:這個函數是你交給别人使用的。

1.3. 最大限度地少改動!

既然如此,我們就來想想辦法不修改調用的代碼;如果不修改調用代碼,也就意味着調用foo()需要産生調用timeit(foo)的效果。我們可以想到将timeit指派給foo,但是timeit似乎帶有一個參數……想辦法把參數統一吧!如果timeit(foo)不是直接産生調用效果,而是傳回一個與foo參數清單一緻的函數的話……就很好辦了,将timeit(foo)的傳回值指派給foo,然後,調用foo()的代碼完全不用修改!

#-*- coding: utf-8 -*-

# 定義一個計時器,傳入一個,并傳回另一個附加了計時功能的方法

    # 定義一個内嵌的包裝函數,給傳入的函數加上計時功能的包裝

    def wrapper():

        start = time.clock()

        func()

        end =time.clock()

        print 'used:', end - start

    # 将包裝後的函數傳回

    return wrapper

foo = timeit(foo)

這樣,一個簡易的計時器就做好了!我們隻需要在定義foo以後調用foo之前,加上foo = timeit(foo),就可以達到計時的目的,這也就是裝飾器的概念,看起來像是foo被timeit裝飾了。在在這個例子中,函數進入和退出時需要計時,這被稱為一個橫切面(aspect),這種程式設計方式被稱為面向切面的程式設計(aspect-oriented programming)。與傳統程式設計習慣的從上往下執行方式相比較而言,像是在函數執行的流程中橫向地插入了一段邏輯。在特定的業務領域裡,能減少大量重複代碼。面向切面程式設計還有相當多的術語,這裡就不多做介紹,感興趣的話可以去找找相關的資料。

這個例子僅用于示範,并沒有考慮foo帶有參數和有傳回值的情況,完善它的重任就交給你了 :)

上面這段代碼看起來似乎已經不能再精簡了,python于是提供了一個文法糖來降低字元輸入量。

@timeit

重點關注第11行的@timeit,在定義上加上這一行與另外寫foo = timeit(foo)完全等價,千萬不要以為@有另外的魔力。除了字元輸入少了一些,還有一個額外的好處:這樣看上去更有裝飾器的感覺。

要了解python的裝飾器,我們首先必須明白在python中函數也是被視為對象(在python中一切都是對象)。在python中一切都是對象這一點很重要。先看一個例子:

def shout(word="yes") :

    return word.capitalize()+" !"

print shout()

# 輸出 : 'yes !'

# 作為一個對象,你可以把函數賦給任何其他對象變量

scream = shout

# 注意我們沒有使用圓括号,因為我們不是在調用函數

# 我們把函數shout賦給scream,也就是說你可以通過scream調用shout

print scream()

# 還有,你可以删除舊的名字shout,但是你仍然可以通過scream來通路該函數

del shout

try :

    print shout()

except nameerror, e :

    print e

    #輸出 : "name 'shout' is not defined"

我們暫且把這個話題放旁邊,我們先看看python另外一個很有意思的屬性:可以在函數中定義函數:

def talk() :

    # 你可以在talk中定義另外一個函數

    def whisper(word="yes") :

        return word.lower()+"...";

    # ... 并且立馬使用它

    print whisper()

# 你每次調用'talk',定義在talk裡面的whisper同樣也會被調用

talk()

# 輸出 :

# yes...

# 但是"whisper" 不會單獨存在:

    #輸出 : "name 'whisper' is not defined"*

函數引用

從以上兩個例子我們可以得出,函數既然作為一個對象,是以:

1. 其可以被賦給其他變量

2. 其可以被定義在另外一個函數内

這也就是說,函數可以傳回一個函數,看下面的例子:

def gettalk(type="shout") :

    # 我們定義另外一個函數

    def shout(word="yes") :

        return word.capitalize()+" !"

    # 然後我們傳回其中一個

    if type == "shout" :

        # 我們沒有使用(),因為我們不是在調用該函數

        # 我們是在傳回該函數

        return shout

    else :

        return whisper

# 然後怎麼使用呢 ?

# 把該函數賦予某個變量

talk = gettalk()    

# 這裡你可以看到talk其實是一個函數對象:

print talk

#輸出 : <function shout at 0xb7ea817c>

# 該對象由函數傳回的其中一個對象:

print talk()

# 或者你可以直接如下調用 :

print gettalk("whisper")()

#輸出 : yes...

還有,既然可以傳回一個函數,我們可以把它作為參數傳遞給函數:

def dosomethingbefore(func) :

    print "i do something before then i call the function you gave me"

    print func()

dosomethingbefore(scream)

#輸出 :

#i do something before then i call the function you gave me

#yes !

這裡你已經足夠能了解裝飾器了,其他它可被視為封裝器。也就是說,它能夠讓你在裝飾前後執行代碼而無須改變函數本身内容。

手工裝飾

那麼如何進行手動裝飾呢?

# 裝飾器是一個函數,而其參數為另外一個函數

def my_shiny_new_decorator(a_function_to_decorate) :

    # 在内部定義了另外一個函數:一個封裝器。

    # 這個函數将原始函數進行封裝,是以你可以在它之前或者之後執行一些代碼

    def the_wrapper_around_the_original_function() :

        # 放一些你希望在真正函數執行前的一些代碼

        print "before the function runs"

        # 執行原始函數

        a_function_to_decorate()

        # 放一些你希望在原始函數執行後的一些代碼

        print "after the function runs"

    #在此刻,"a_function_to_decrorate"還沒有被執行,我們傳回了建立的封裝函數

    #封裝器包含了函數以及其前後執行的代碼,其已經準備完畢

    return the_wrapper_around_the_original_function

# 現在想象下,你建立了一個你永遠也不遠再次接觸的函數

def a_stand_alone_function() :

    print "i am a stand alone function, don't you dare modify me"

a_stand_alone_function()

#輸出: i am a stand alone function, don't you dare modify me

# 好了,你可以封裝它實作行為的擴充。可以簡單的把它丢給裝飾器

# 裝飾器将動态地把它和你要的代碼封裝起來,并且傳回一個新的可用的函數。

a_stand_alone_function_decorated = my_shiny_new_decorator(a_stand_alone_function)

a_stand_alone_function_decorated()

#before the function runs

#i am a stand alone function, don't you dare modify me

#after the function runs

現在你也許要求當每次調用a_stand_alone_function時,實際調用卻是a_stand_alone_function_decorated。實作也很簡單,可以用my_shiny_new_decorator來給a_stand_alone_function重新指派。

a_stand_alone_function = my_shiny_new_decorator(a_stand_alone_function)

# and guess what, that's exactly what decorators do !

裝飾器揭秘

前面的例子,我們可以使用裝飾器的文法:

@my_shiny_new_decorator

def another_stand_alone_function() :

    print "leave me alone"

another_stand_alone_function()

#leave me alone

當然你也可以累積裝飾:

def bread(func) :

    def wrapper() :

        print "</''''''\>"

        print "<\______/>"

def ingredients(func) :

        print "#tomatoes#"

        print "~salad~"

def sandwich(food="--ham--") :

    print food

sandwich()

#輸出 : --ham--

sandwich = bread(ingredients(sandwich))

#outputs :

#</''''''\>

# #tomatoes#

# --ham--

# ~salad~

#<\______/>

使用python裝飾器文法:

@bread

@ingredients

裝飾器的順序很重要,需要注意:

def strange_sandwich(food="--ham--") :

strange_sandwich()

##tomatoes#

最後回答前面提到的問題:

# 裝飾器makebold用于轉換為粗體

    # 結果傳回該函數

        # 插入一些執行前後的代碼

# 裝飾器makeitalic用于轉換為斜體

    return "hello"

print say()

#輸出: <b><i>hello</i></b>

# 等同于

say = makebold(makeitalic(say))

内置的裝飾器

内置的裝飾器有三個,分别是staticmethod、classmethod和property,作用分别是把類中定義的執行個體方法變成靜态方法、類方法和類屬性。由于子產品裡可以定義函數,是以靜态方法和類方法的用處并不是太多,除非你想要完全的面向對象程式設計。而屬性也不是不可或缺的,java沒有屬性也一樣活得很滋潤。從我個人的python經驗來看,我沒有使用過property,使用staticmethod和classmethod的頻率也非常低。

class rabbit(object):

    def __init__(self, name):

        self._name = name

    @staticmethod

    def newrabbit(name):

        return rabbit(name)

    @classmethod

    def newrabbit2(cls):

        return rabbit('')

    @property

    def name(self):

        return self._name

這裡定義的屬性是一個隻讀屬性,如果需要可寫,則需要再定義一個setter:

@name.setter

def name(self, name):

    self._name = name

functools子產品

functools子產品提供了兩個裝飾器。這個子產品是python 2.5後新增的,一般來說大家用的應該都高于這個版本。但我平時的工作環境是2.4 t-t

2.3.1. wraps(wrapped[, assigned][, updated]):

這是一個很有用的裝飾器。看過前一篇反射的朋友應該知道,函數是有幾個特殊屬性比如函數名,在被裝飾後,上例中的函數名foo會變成包裝函數的名字wrapper,如果你希望使用反射,可能會導緻意外的結果。這個裝飾器可以解決這個問題,它能将裝飾過的函數的特殊屬性保留。

import functools

    @functools.wraps(func)

print foo.__name__

首先注意第5行,如果注釋這一行,foo.__name__将是'wrapper'。另外相信你也注意到了,這個裝飾器竟然帶有一個參數。實際上,他還有另外兩個可選的參數,assigned中的屬性名将使用指派的方式替換,而updated中的屬性名将使用update的方式合并,你可以通過檢視functools的源代碼獲得它們的預設值。對于這個裝飾器,相當于wrapper = functools.wraps(func)(wrapper)。

2.3.2. total_ordering(cls):

這個裝飾器在特定的場合有一定用處,但是它是在python 2.7後新增的。它的作用是為實作了至少__lt__、__le__、__gt__、__ge__其中一個的類加上其他的比較方法,這是一個類裝飾器。如果覺得不好了解,不妨仔細看看這個裝飾器的源代碼:

def total_ordering(cls):

      """class decorator that fills in missing ordering methods"""

      convert = {

          '__lt__': [('__gt__', lambda self, other: other < self),

                     ('__le__', lambda self, other: not other < self),

                     ('__ge__', lambda self, other: not self < other)],

          '__le__': [('__ge__', lambda self, other: other <= self),

                     ('__lt__', lambda self, other: not other <= self),

                     ('__gt__', lambda self, other: not self <= other)],

          '__gt__': [('__lt__', lambda self, other: other > self),

                     ('__ge__', lambda self, other: not other > self),

                     ('__le__', lambda self, other: not self > other)],

          '__ge__': [('__le__', lambda self, other: other >= self),

                     ('__gt__', lambda self, other: not other >= self),

                     ('__lt__', lambda self, other: not self >= other)]

      }

      roots = set(dir(cls)) & set(convert)

      if not roots:

          raise valueerror('must define at least one ordering operation: < > <= >=')

      root = max(roots)       # prefer __lt__ to __le__ to __gt__ to __ge__

      for opname, opfunc in convert[root]:

          if opname not in roots:

              opfunc.__name__ = opname

              opfunc.__doc__ = getattr(int, opname).__doc__

              setattr(cls, opname, opfunc)

      return cls

<a target="_blank" href="http://blog.csdn.net/mdl13412/article/details/22608283">淺談python裝飾器</a>

淺談python裝飾器

by 馬冬亮(凝霜  loki)

python将一切視為 objec t的子類,即一切都是對象,是以函數可以像變量一樣被指向和傳遞,我們來看下面的例子:
foo.def foo():     pass print issubclass(foo.__class__, object)__class__, object)   其運作結果如下:
Python中的裝飾器用法詳解
true   上述代碼說明了python 中的函數是 object 的子類,下面讓我們看函數被當作參數傳遞時的效果:
Python中的裝飾器用法詳解
def foo(func):       func()   def bar():       print "bar"   foo(bar)  
Python中的裝飾器用法詳解
bar  
python中通過提供 namespace 來實作重名函數/方法、變量等資訊的識别,其一共有三種 namespace,分别為: local namespace: 作用範圍為目前函數或者類方法 global namespace: 作用範圍為目前子產品 build-in namespace: 作用範圍為所有子產品 當函數/方法、變量等資訊發生重名時,python會按照 local namespace -&gt; global namespace -&gt; build-in namespace的順序搜尋使用者所需元素,并且以第一個找到此元素的 namespace 為準。 下面以系統的 build-in 函數 str 為例進行說明:
Python中的裝飾器用法詳解
def str(s):       print "global str()"   def foo():       def str(s):           print "closure str()"       str("dummy")   foo()   bar()   首先定義三個 global namespace 的函數 str、foo 和bar,然後在 foo 函數中定義一個内嵌的 local namespace 的函數str,然後在函數 foo 和bar 中分别調用 str("dummy"),其運作結果如下所示:
Python中的裝飾器用法詳解
closure str()   global str()   通過編碼實驗,我們可以看到: foo 中調用 str 函數時,首先搜尋 local namespace,并且成功找到了所需的函數,停止搜尋,使用此namespace 中的定義 bar 中調用 str 函數時,首先搜尋 local namespace,但是沒有找到str 方法的定義,是以繼續搜尋 global namespace,并成功找到了str 的定義,停止搜尋,并使用此定義 下面我們使用python内置的 `ocals() 和 globals() 函數檢視不同 namespace 中的元素定義:
Python中的裝飾器用法詳解
var = "var in global"   def fun():       var = "var in fun"       print "fun: " + str(locals())   print "globals: " + str(globals())   fun()   運作結果如下:
Python中的裝飾器用法詳解
globals: {'__builtins__': &lt;module '__builtin__' (built-in)&gt;, '__file__': 'a.py', '__package__': none, 'fun': &lt;function fun at 0x7f2ca74f66e0&gt;, 'var': 'var in global', '__name__': '__main__', '__doc__': none}   fun: {'var': 'var in fun'}   通過運作結果,我們看到了 fun 定義了 local namespace 的變量var,在 global namespace 有一個全局的 var 變量,那麼當在global namespace 中直接通路 var 變量的時候,将會得到 var = "var in global" 的定義,而在fun 函數的 local namespace 中通路 var 變量,則會得到fun 私有的 var = "var in fun" 定義。
*args: 把所有的參數按出現順序打包成一個 list **kwargs:把所有 key-value 形式的參數打包成一個 dict
下面給出一個 *args 的例子:
Python中的裝飾器用法詳解
params_list = (1, 2)   params_tupple = (1, 2)   def add(x, y):       print x + y   add(*params_list)   add(*params_tupple)  
Python中的裝飾器用法詳解
3   **kwargs 的例子:
Python中的裝飾器用法詳解
params = {       'x': 1,       'y': 2   }   add(**params)  
Python中的裝飾器用法詳解
Python中的裝飾器用法詳解
&lt;a target="_blank" href="http://zh.wikipedia.org/zh-cn/%e9%97%ad%e5%8c%85_(%e8%ae%a1%e7%ae%97%e6%9c%ba%e7%a7%91%e5%ad%a6)"&gt;閉包在維基百科上的定義&lt;/a&gt;如下: 在計算機科學中,閉包(closure)是詞法閉包(lexical closure)的簡稱,是引用了自由變量的函數。這個被引用的自由變量将和這個函數一同存在,即使已經離開了創造它的環境也不例外。是以,有另一種說法認為閉包是由函數和與其相關的引用環境組合而成的實體。   下面給出一個使用閉包實作的logger factory的例子:
Python中的裝飾器用法詳解
def logger_facroty(prefix="", with_prefix=true):       if with_prefix:           def logger(msg):               print prefix + msg           return logger       else:               print msg   logger_with_prefix = logger_facroty("prefix: ")   logger_without_prefix = logger_facroty(with_prefix=false)   logger_with_prefix("msg")   logger_without_prefix("msg")  
Python中的裝飾器用法詳解
prefix: msg   msg   在上面這個閉包的例子中,prefix 變量時所謂的自由變量,其在 return logger 執行完畢後,便脫離了建立它的環境logger_factory,但因為其被 logger_factory 中定義的 logger 函數所引用,其生命周期将至少和 logger 函數相同。這樣,在 logger 中就可以引用到logger_factory 作用域内的變量 prefix。 将閉包與 namespace 結合起來:
Python中的裝飾器用法詳解
def fun_outer():       var = "var in fun_outer"       unused_var = "this var is not used in fun_inner"       print "fun_outer: " + var       print "fun_outer: " + str(locals())       print "fun_outer: " + str(id(var))       def fun_inner():           print "fun_inner: " + var           print "fun_inner: " + str(locals())           print "fun_inner: " + str(id(var))       return fun_inner   fun_outer()()  
Python中的裝飾器用法詳解
fun_outer: var in fun_outer   fun_outer: {'var': 'var in fun_outer', 'unused_var': 'this var is not used in fun_inner'}   fun_outer: 140228141915584   fun_inner: var in fun_outer   fun_inner: {'var': 'var in fun_outer'}   fun_inner: 140228141915584   在這個例子中,當 fun_outer 被定義時,其内部的定義的 fun_inner 函數對 print "fun_inner: " + var中所引用的 var 變量進行搜尋,發現第一個被搜尋到的 var 定義在 fun_outer 的 local namespace 中,是以使用此定義,通過 print "fun_outer: " + str(id(var)) 和 print "fun_inner: " + str(id(var)),當var 超出 fun_outer 的作用域後,依然存活,而 fun_outer 中的unused_var 變量由于沒有被 fun_inner 所引用,是以會被gc。
Python中的裝飾器用法詳解
&lt;a target="_blank" href="http://en.wikipedia.org/wiki/python_syntax_and_semantics#decorators"&gt;點選打開裝飾器在維基百科上的定義連結&lt;/a&gt;如下: a decorator is any callable python object that is used to modify a function, method or class definition.   
Python中的裝飾器用法詳解
@bar       print "foo"   其等價于:
Python中的裝飾器用法詳解
foo = bar(foo)  
Python中的裝飾器用法詳解
    print 'decorator foo'       return func   @foo       print 'bar'   foo 函數被用作裝飾器,其本身接收一個函數對象作為參數,然後做一些工作後,傳回接收的參數,供外界調用。 注意: 時刻牢記 @foo 隻是一個文法糖,其本質是 foo = bar(foo)
Python中的裝飾器用法詳解
import time   def function_performance_statistics(trace_this=true):       if trace_this:          def performace_statistics_delegate(func):               def counter(*args, **kwargs):                   start = time.clock()                   func(*args, **kwargs)                   end =time.clock()                   print 'used time: %d' % (end - start, )               return counter               return func       return performace_statistics_delegate   @function_performance_statistics(true)       time.sleep(3)       print 'add result: %d' % (x + y,)   @function_performance_statistics(false)   def mul(x, y=1):       print 'mul result: %d' % (x * y,)   add(1, 1)   mul(10)   上述代碼想要實作一個性能分析器,并接收一個參數,來控制性能分析器是否生效,其運作效果如下所示:
Python中的裝飾器用法詳解
add result: 2   used time: 0   mul result: 10   上述代碼中裝飾器的調用等價于:
Python中的裝飾器用法詳解
add = function_performance_statistics(true)(add(1, 1))   mul = function_performance_statistics(false)(mul(10))  
類的裝飾器不常用,是以隻簡單介紹。
Python中的裝飾器用法詳解
def bar(dummy):   def inject(cls):       cls.bar = bar       return cls   @inject   class foo(object):       pass   foo = foo()   foo.bar()   上述代碼的 inject 裝飾器為類動态的添加一個 bar 方法,因為類在調用非靜态方法的時候會傳進一個self 指針,是以 bar 的第一個參數我們簡單的忽略即可,其運作結果如下:
Python中的裝飾器用法詳解
類裝飾器相比函數裝飾器,具有靈活度大,高内聚、封裝性等優點。其實作起來主要是靠類内部的 __call__ 方法,當使用 @ 形式将裝飾器附加到函數上時,就會調用此方法,下面時一個執行個體:
Python中的裝飾器用法詳解
    def __init__(self, func):           super(foo, self).__init__()           self._func = func       def __call__(self):           print 'class decorator'           self._func()  
Python中的裝飾器用法詳解
class decorator  
python中内置的裝飾器有三個: staticmethod、classmethod 和property staticmethod 是類靜态方法,其跟成員方法的差別是沒有 self 指針,并且可以在類不進行執行個體化的情況下調用,下面是一個執行個體,對比靜态方法和成員方法
Python中的裝飾器用法詳解
    @staticmethod       def statc_method(msg):           print msg       def member_method(self, msg):   foo.member_method('some msg')   foo.statc_method('some msg')  
Python中的裝飾器用法詳解
some msg   classmethod 與成員方法的差別在于所接收的第一個參數不是 self 類執行個體的指針,而是目前類的具體類型,下面是一個執行個體:
Python中的裝飾器用法詳解
    @classmethod       def class_method(cls):           print repr(cls)       def member_method(self):           print repr(self)   foo.class_method()   foo.member_method()  
Python中的裝飾器用法詳解
&lt;class '__main__.foo'&gt;   &lt;__main__.foo object at 0x10a611c50&gt;   property 是屬性的意思,即可以通過通過類執行個體直接通路的資訊,下面是具體的例子:
Python中的裝飾器用法詳解
    def __init__(self, var):           self._var = var       @property       def var(self):           return self._var       @var.setter       def var(self, var):   foo = foo('var 1')   print foo.var   foo.var = 'var 2'   注意: 如果将上面的 @var.setter 裝飾器所裝飾的成員函數去掉,則foo.var 屬性為隻讀屬性,使用 foo.var = 'var 2' 進行指派時會抛出異常,其運作結果如下:
Python中的裝飾器用法詳解
var 1   var 2   注意: 如果使用老式的python類定義,所聲明的屬性不是 read only的,下面代碼說明了這種情況:
Python中的裝飾器用法詳解
class foo:  
Python中的裝飾器用法詳解
裝飾器的調用順序與使用 @ 文法糖聲明的順序相反,如下所示:
Python中的裝飾器用法詳解
def decorator_a(func):       print "decorator_a"   def decorator_b(func):       print "decorator_b"   @decorator_a   @decorator_b  
Python中的裝飾器用法詳解
foo = decorator_a(decorator_b(foo))   通過等價的調用形式我們可以看到,按照python的函數求值序列,decorator_b(fun) 會首先被求值,然後将其結果作為輸入,傳遞給decorator_a,是以其調用順序與聲明順序相反。其運作結果如下所示:
Python中的裝飾器用法詳解
decorator_b   decorator_a   foo  
裝飾器很好用,那麼它什麼時候被調用?性能開銷怎麼樣?會不會有副作用?接下來我們就以幾個執行個體來驗證我們的猜想。 首先我們驗證一下裝飾器的性能開銷,代碼如下所示:
Python中的裝飾器用法詳解
    print 'func id: ' + str(id(func))   print 'begin declare foo with decorators'   print 'end declare foo with decorators'   print 'first call foo'   print 'second call foo'   print 'function infos'   print 'decorator_a id: ' + str(id(decorator_a))   print 'decorator_b id: ' + str(id(decorator_b))   print 'fooid : ' + str(id(foo))  
Python中的裝飾器用法詳解
begin declare foo with decorators   func id: 140124961990488   end declare foo with decorators   first call foo   second call foo   function infos   decorator_a id: 140124961954464   decorator_b id: 140124961988808   fooid : 140124961990488   在運作結果中的:
Python中的裝飾器用法詳解
證明了裝飾器的調用時機為: 被裝飾對象定義時 而運作結果中的:
Python中的裝飾器用法詳解
證明了在相同 .py 檔案中,裝飾器對所裝飾的函數隻進行一次裝飾,不會每次調用相應函數時都重新裝飾,這個很容易了解,因為其本質等價于下面的函數簽名重新綁定:
Python中的裝飾器用法詳解
對于跨子產品的調用,我們編寫如下結構的測試代碼:
Python中的裝飾器用法詳解
.   ├── common   │   ├── decorator.py   │   ├── __init__.py   │   ├── mod_a   │   │   ├── fun_a.py   │   │   └── __init__.py   │   └── mod_b   │       ├── fun_b.py   │       └── __init__.py   └── test.py   上述所有子產品中的 __init__.py 檔案均為: # -*- coding: utf-8 -*-
Python中的裝飾器用法詳解
# -*- coding: utf-8 -*-   # common/mod_a/fun_a.py   from common.decorator import foo   def fun_a():       print 'in common.mod_a.fun_a.fun_a call foo'       foo()  
Python中的裝飾器用法詳解
# common/mod_b/fun_b.py   def fun_b():       print 'in common.mod_b.fun_b.fun_b call foo'  
Python中的裝飾器用法詳解
# common/decorator.py       print 'init decorator_a'       print 'function foo'  
Python中的裝飾器用法詳解
# test.py   from common.mod_a.fun_a import fun_a   from common.mod_b.fun_b import fun_b   fun_a()   fun_b()   上述代碼通過建立 common.mod_a 和 common.mod_b 兩個子子產品,并調用common.decorator 中的 foo 函數,來測試跨子產品時裝飾器的工作情況,運作 test.py 的結果如下所示:
Python中的裝飾器用法詳解
init decorator_a   in common.mod_a.fun_a.fun_a call foo   function foo   in common.mod_b.fun_b.fun_b call foo   經過上面的驗證,可以看出,對于跨子產品的調用,裝飾器也隻會初始化一次,不過這要歸功于 *.pyc,這與本文主題無關,故不詳述。 關于裝飾器副作用的話題比較大,這不僅僅是裝飾器本身的問題,更多的時候是我們設計上的問題,下面給出一個初學裝飾器時大家都會遇到的一個問題——丢失函數元資訊:
Python中的裝飾器用法詳解
    def inner(*args, **kwargs):           res = func(*args, **kwargs)           return res       return inner       '''''foo doc'''       return 'foo result'   print 'foo.__module__: ' + str(foo.__module__)   print 'foo.__name__: ' + str(foo.__name__)   print 'foo.__doc__: ' + str(foo.__doc__)   print foo()   其運作結果如下所示:
Python中的裝飾器用法詳解
foo.__module__: __main__   foo.__name__: inner   foo.__doc__: none   foo result  
前面已經講解過裝飾器的調用順序和調用時機,但是被多個裝飾器裝飾的函數,其運作期行為還是有一些細節需要說明的,而且很可能其行為會讓你感到驚訝,下面時一個執行個體:
Python中的裝飾器用法詳解
def tracer(msg):       print "[trace] %s" % msg   def logger(func):       tracer("logger")       def inner(username, password):           tracer("inner")           print "call %s" % func.__name__           return func(username, password)   def login_debug_helper(show_debug_info=false):       tracer("login_debug_helper")       def proxy_fun(func):           tracer("proxy_fun")           def delegate_fun(username, password):               tracer("delegate_fun")               if show_debug_info:                   print "username: %s\npassword: %s" % (username, password)               return func(username, password)           return delegate_fun       return proxy_fun   print 'declaring login_a'   @logger   @login_debug_helper(show_debug_info=true)   def login_a(username, password):       tracer("login_a")       print "do some login authentication"       return true   print 'call login_a'   login_a("mdl", "pwd")   大家先來看一下運作結果,看看是不是跟自己想象中的一緻:
Python中的裝飾器用法詳解
declaring login_a   [trace] login_debug_helper   [trace] proxy_fun   [trace] logger   call login_a   [trace] inner   call delegate_fun   [trace] delegate_fun   username: mdl   password: pwd   [trace] login_a   do some login authentication   首先,裝飾器初始化時的調用順序與我們前面講解的一緻,如下:
Python中的裝飾器用法詳解
然而,接下來,來自 logger 裝飾器中的 inner 函數首先被執行,然後才是login_debug_helper 傳回的 proxy_fun 中的 delegate_fun 函數。各位讀者發現了嗎,運作期執行login_a 函數的時候,裝飾器中傳回的函數的執行順序是相反的,難道是我們前面講解的例子有錯誤嗎?其實,如果大家的認為運作期調用順序應該與裝飾器初始化階段的順序一緻的話,那說明大家沒有看透這段代碼的調用流程,下面我來為大家分析一下。
Python中的裝飾器用法詳解
當裝飾器 login_debug_helper 被調用時,其等價于:
Python中的裝飾器用法詳解
login_debug_helper(show_debug_info=true)(login_a)('mdl', 'pwd')   對于隻有 login_debug_helper 的情況,現在就應該是執行玩login_a輸出結果的時刻了,但是如果現在在加上logger 裝飾器的話,那麼這個login_debug_helper(show_debug_info=true)(login_a)('mdl', 'pwd')就被延遲執行,而将login_debug_helper(show_debug_info=true)(login_a) 作為參數傳遞給 logger,我們令 login_tmp = login_debug_helper(show_debug_info=true)(login_a),則調用過程等價于:
Python中的裝飾器用法詳解
login_tmp = login_debug_helper(show_debug_info=true)(login_a)   login_a = logger(login_tmp)   login_a('mdl', 'pwd')   相信大家看過上面的等價變換後,已經明白問題出在哪裡了,如果你還沒有明白,我強烈建議你把這個例子自己敲一遍,并嘗試用自己的方式進行化簡,逐漸得出結論。