關(guān)于Python Decorator你應(yīng)該知道的一切

關(guān)鍵詞:Python橱脸、裝飾器础米、裝飾器的深入討論分苇、Python decorator

前言

最近學(xué)習(xí)Python,在看一個框架源碼過程中對裝飾器很困惑,Google了一圈椭盏,在stack overflow的一個問題討論下面找到了這個總結(jié)组砚,這里幾乎有關(guān)于Python全部的內(nèi)容。覺得很好掏颊,遂翻譯過來糟红。翻譯基本都是意譯,能看英文的還是盡量看上面鏈接過去的原版吧乌叶!

Pyton裝飾器基礎(chǔ)

在Python中盆偿,函數(shù)也是對象

為了理解裝飾器,你必須首先理解准浴,在Python中函數(shù)也是對象事扭。
理解這個知識點很重要。讓我們使用一個簡單的例子來說明一下:

def shout(word="yes"):
    return word.capitalize()+"!"

print shout()
# 輸出為: 'Yes!'


# 函數(shù)作為一個對象乐横,你可以像其他對象一樣求橄,把它賦值給其他的變量
scream = shout

# 注意我們沒有使用圓括號:我們不是調(diào)用這個函數(shù),我們把"shout"這個函數(shù)賦值給變量"scream"   
# 那意味著你之后可以使用"scream"來調(diào)用"shout"這個函數(shù)
print scream()
# 輸出為: 'Yes!'

# 不僅如此葡公,那意味著你可以移除'shout'這個老的名稱罐农,但這個函數(shù)仍然可以通過'scream'訪問
del shout
try:
    print shout()
except NameError, e:
    print e
    # 輸出為: "name 'shout' is not defined"

print scream()
# 輸出為: 'Yes!'

好了,在心里記住這個知識點催什。我們之后很快要用到它涵亏。

Python中函數(shù)還有另一個有趣的特性,那就是它可以在其他函數(shù)里面定義蒲凶!

def talk():

    # 你可以在"talk"函數(shù)中定義一個函數(shù)...
    def whisper(word="yes"):
        return word.lower()+"..."

     # ...并且你可以馬上使用這個函數(shù)
    print whisper()

# 你每次調(diào)用"talk"這個函數(shù)的時候气筋,它會定義一個"whisper"函數(shù),之后這個"whisper"將在"talk"里面被調(diào)用
talk()
# 輸出為:"yes..."

# 但是在"talk"這個函數(shù)的作用域之外旋圆,"whisper"這個函數(shù)是不存在的
try:
    print whisper()
except NameError, e:
    print e
    # 輸出為: "name 'whisper' is not defined"*

函數(shù)的引用

Okay宠默,就這些東西嗎?有趣的部分該上場了...
你已經(jīng)看見灵巧,函數(shù)是對象光稼。因此,函數(shù):

  • 可以賦值給其他變量
  • 可以在其它函數(shù)里面定義

那意味著一個函數(shù)可以被另一個函數(shù)return孩等。我們來看個例子! ?

def getTalk(kind="shout"):

    # 我們定義了一些函數(shù)
    def shout(word="yes"):
        return word.capitalize()+"!"

    def whisper(word="yes") :
        return word.lower()+"...";

    # 然后我們返回他們中的一個
    if kind == "shout":
        # 我們沒有用"()", 我們不是要調(diào)用這個函數(shù)
        # 我們返回了這個函數(shù)對象
        return shout  
    else:
        return whisper

# 我們怎么使用它呢?
# 獲取函數(shù)艾君,并將它賦值給一個變量
talk = getTalk()      

# 你可以看到在這里"talk"是一個函數(shù)對象:
print talk
# 輸出為: <function shout at 0xb7ea817c>

# 這個就是被函數(shù)返回的對象
print talk()
# 輸出為: Yes!

# 你甚至可以直接使用它:
print getTalk("whisper")()
# 輸出為: yes...

等等...這里有我們沒有注意到的地方!

既然你可以return一個函數(shù),你就可以把一個函數(shù)當(dāng)參數(shù)傳遞:

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!

好了肄方,你已經(jīng)具備了理解裝飾器的所有知識點冰垄。你知道,裝飾器就是 "封裝", 這意味著它可以讓你在被它裝飾的函數(shù)前面和后面執(zhí)行一些代碼,而不必改動被裝飾的函數(shù)本身虹茶。

手動創(chuàng)建裝飾器

你如何手動構(gòu)建一個裝飾器:

# 裝飾是一個函數(shù)逝薪,該函數(shù)需要另一個函數(shù)作為它的參數(shù)
def my_shiny_new_decorator(a_function_to_decorate):

    # 在裝飾器的函數(shù)實現(xiàn)里面它定義了另一個函數(shù): 他就是封裝函數(shù)(wrapper)
    # 這個函數(shù)將原來的函數(shù)封裝到里面
    # 因此你可以在原來函數(shù)的前面和后面執(zhí)行一些附加代碼
    def the_wrapper_around_the_original_function():

        # 在這里放置你想在原來函數(shù)執(zhí)行前執(zhí)行的代碼
        print "Before the function runs"

        # 調(diào)用原來的函數(shù)(使用圓括號)
        a_function_to_decorate()

        # 在這里放置你想在原來函數(shù)執(zhí)行后執(zhí)行的代碼
        print "After the function runs"

    # 這個時候,"a_function_to_decorate"并沒有執(zhí)行
    # 我們返回剛才創(chuàng)建的封裝函數(shù)
    # 這個封裝函數(shù)包含了原來的函數(shù)蝴罪,和將在原來函數(shù)前面和后面執(zhí)行的代碼董济。我們就可以使用它了!
    return the_wrapper_around_the_original_function

# 想象你創(chuàng)建了一個你再也不想修改的函數(shù)
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

# 現(xiàn)在你可以裝飾這個函數(shù)來擴(kuò)展它的行為
# 只需要將這個函數(shù)傳入裝飾器,那它將被動態(tài)的包在任何你想執(zhí)行的代碼間要门,并且返回一個可被使用的新函數(shù):
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

現(xiàn)在虏肾,你可能想在每次調(diào)用 a_stand_alone_function的時候,真正被執(zhí)行的函數(shù)是 a_stand_alone_function_decorated欢搜。那很容易封豪,只需要使用 my_shiny_new_decorator返回的函數(shù)賦值給原來的 a_stand_alone_function這個函數(shù)名(其實是個變量):

a_stand_alone_function = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function()
#輸出為:
#Before the function runs
#I am a stand alone function, don't you dare modify me
#After the function runs

# 你猜怎么著?這就是裝飾器做的事情炒瘟。

裝飾器揭秘

前面的例子吹埠,使用Python的裝飾器語法糖來重寫就是下面的樣子:

@my_shiny_new_decorator
def another_stand_alone_function():
    print "Leave me alone"

another_stand_alone_function()  
# 輸出為:  
#Before the function runs
#Leave me alone
#After the function runs

是的,這就是全部疮装,就是這么簡單缘琅。@decorator 只是下面表達(dá)式的簡寫:

another_stand_alone_function = my_shiny_new_decorator(another_stand_alone_function)

這里的裝飾器只是裝飾器設(shè)計模式的一種Python化變體。Python嵌入了多種經(jīng)典的設(shè)計模式來簡化開發(fā)(比如迭代器(iterators))廓推。

當(dāng)然刷袍,你可以堆積裝飾器(使用多層裝飾器):

def bread(func):
    def wrapper():
        print "</''''''\>"
        func()
        print "<\______/>"
    return wrapper

def ingredients(func):
    def wrapper():
        print "#tomatoes#"
        func()
        print "~salad~"
    return wrapper

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

sandwich()
# 輸出為: --ham--
sandwich = bread(ingredients(sandwich))
sandwich()
#outputs:
#</''''''\>
# #tomatoes#
# --ham--
# ~salad~
#<\______/>

使用Python的裝飾器語法糖:

@bread
@ingredients
def sandwich(food="--ham--"):
    print food

sandwich()
#outputs:
#</''''''\>
# #tomatoes#
# --ham--
# ~salad~
#<\______/>

你放置裝飾器的順序很重要:

@ingredients
@bread
def strange_sandwich(food="--ham--"):
    print food

strange_sandwich()
#outputs:
##tomatoes#
#</''''''\>
# --ham--
#<\______/>
# ~salad~

現(xiàn)在: 回答問題(請參考stack overflorw上的相關(guān)問題)

作為結(jié)論,你可以很容易看出如何回答問題:

# 使其變bold的裝飾器
def makebold(fn):
    # 裝飾器將要返回的函數(shù)
    def wrapper():
        # 在原函數(shù)前面和后面插入一些代碼
        return "<b>" + fn() + "</b>"
    return wrapper

# 使其變italic的裝飾器
def makeitalic(fn):
      # 裝飾器將要返回的函數(shù)
    def wrapper():
        # 在原函數(shù)前面和后面插入一些代碼
        return "<i>" + fn() + "</i>"
    return wrapper

@makebold
@makeitalic
def say():
    return "hello"

print say() 
# 輸出為: <b><i>hello</i></b>

# 這和下面代碼效果相同
def say():
    return "hello"
say = makebold(makeitalic(say))

print say() 
# 輸出為: <b><i>hello</i></b>

你可以高興的離開這里了受啥,或者再費點腦子來看看裝飾器的高級用法做个。


更深入的討論裝飾器

向被裝飾的函數(shù)傳參數(shù)

# 這不是黑魔法鸽心,你只需要讓封裝函數(shù)傳遞這些參數(shù):

def a_decorator_passing_arguments(function_to_decorate):
    def a_wrapper_accepting_arguments(arg1, arg2):
        print "I got args! Look:", arg1, arg2
        function_to_decorate(arg1, arg2)
    return a_wrapper_accepting_arguments

# 因為當(dāng)你調(diào)用被裝飾器返回的函數(shù)時滚局,實際你是在調(diào)用封裝函數(shù)  
# 所以向封裝函數(shù)傳遞參數(shù)可以讓封裝函數(shù)把參數(shù)傳遞給被裝飾的函數(shù)

@a_decorator_passing_arguments
def print_full_name(first_name, last_name):
    print "My name is", first_name, last_name
    
print_full_name("Peter", "Venkman")
# 輸出為:
# I got args! Look: Peter Venkman
# My name is Peter Venkman

裝飾方法

Python中方法和函數(shù)幾乎是一樣的,這個特性很nice顽频。唯一的不同是方法期望它的第一個參數(shù)是對當(dāng)前對象的引用(self)藤肢。

那意味著你可以使用相同的方式來給方法添加裝飾器!只是需要將self考慮在內(nèi):

def method_friendly_decorator(method_to_decorate):
    def wrapper(self, lie):
        lie = lie - 3 # 很友好吧糯景,再次減少了年齡 :-)
        return method_to_decorate(self, lie)
    return wrapper
    
    
class Lucy(object):
    
    def __init__(self):
        self.age = 32
    
    @method_friendly_decorator
    def sayYourAge(self, lie):
        print "I am %s, what did you think?" % (self.age + lie)
        
l = Lucy()
l.sayYourAge(-3)
# 輸出為: I am 26, what did you think?

如果你在寫一個通用的裝飾器--可以接收任何參數(shù)的函數(shù)或者方法--這時候只需要使用 *args, **kwargs:

def a_decorator_passing_arbitrary_arguments(function_to_decorate):
    # 封裝函數(shù)可以接收任何的參數(shù)
    def a_wrapper_accepting_arbitrary_arguments(*args, **kwargs):
        print "Do I have args?:"
        print args
        print kwargs
        # 然后你解包出參數(shù)嘁圈,這里是 *args, **kwargs 
        # 如果你不熟悉怎么解包,可以查看:
        # http://www.saltycrane.com/blog/2008/01/how-to-use-args-and-kwargs-in-python/
        function_to_decorate(*args, **kwargs)
    return a_wrapper_accepting_arbitrary_arguments

@a_decorator_passing_arbitrary_arguments
def function_with_no_argument():
    print "Python is cool, no argument here."

function_with_no_argument()
#輸出為:
#Do I have args?:
#()
#{}
#Python is cool, no argument here.

@a_decorator_passing_arbitrary_arguments
def function_with_arguments(a, b, c):
    print a, b, c
    
function_with_arguments(1,2,3)
# 輸出為:
#Do I have args?:
#(1, 2, 3)
#{}
#1 2 3 
 
@a_decorator_passing_arbitrary_arguments
def function_with_named_arguments(a, b, c, platypus="Why not ?"):
    print "Do %s, %s and %s like platypus? %s" %\
    (a, b, c, platypus)

function_with_named_arguments("Bill", "Linus", "Steve", platypus="Indeed!")
# 輸出為:
#Do I have args ? :
#('Bill', 'Linus', 'Steve')
#{'platypus': 'Indeed!'}
#Do Bill, Linus and Steve like platypus? Indeed!

class Mary(object):
    
    def __init__(self):
        self.age = 31
    
    @a_decorator_passing_arbitrary_arguments
    def sayYourAge(self, lie=-3): # 這時候你可以添加一個默認(rèn)參數(shù)值
        print "I am %s, what did you think ?" % (self.age + lie)

m = Mary()
m.sayYourAge()
# 輸出為:
# Do I have args?:
#(<__main__.Mary object at 0xb7d303ac>,)
#{}
#I am 28, what did you think?

給裝飾器傳遞參數(shù)

好了蟀淮,現(xiàn)在你覺得給裝飾器本身傳遞參數(shù)該怎么做呢?

這個可能有點繞最住,因為裝飾器必須接收一個函數(shù)作為參數(shù)。
因此怠惶,你不能把被裝飾函數(shù)的參數(shù)直接傳遞給裝飾器涨缚。

在我們說出解決辦法前,寫點代碼來找找靈感:

# 裝飾器只是普通的函數(shù)
def my_decorator(func):
    print "I am an ordinary function"
    def wrapper():
        print "I am function returned by the decorator"
        func()
    return wrapper

# 因此策治,你可以不使用任何的 "@" 就可以調(diào)用它

def lazy_function():
    print "zzzzzzzz"

decorated_function = my_decorator(lazy_function)
# 輸出為: I am an ordinary function
            
# 它輸出 "I am an ordinary function"脓魏,因為那就是你在代碼里面做的事情:  
# 調(diào)用一個函數(shù)兰吟,沒有任何的魔法。

@my_decorator
def lazy_function():
    print "zzzzzzzz"
    
# 輸出為: I am an ordinary function

上面兩種方式幾乎一樣茂翔。"my_decorator"被調(diào)用混蔼。因此當(dāng)你在代碼里面添加 @my_decorato時,你就在告訴Python去調(diào)用'被"my_decorator"變量標(biāo)示的函數(shù)'珊燎。

這很重要! 你給出的這個變量名可以直接指向裝飾器-也可以不直接指向惭嚣。

我們來干點邪惡的事情。 ?

def decorator_maker():
    
    print "I make decorators! I am executed only once: "+\
          "when you make me create a decorator."
            
    def my_decorator(func):
        
        print "I am a decorator! I am executed only when you decorate a function."
               
        def wrapped():
            print ("I am the wrapper around the decorated function. "
                  "I am called when you call the decorated function. "
                  "As the wrapper, I return the RESULT of the decorated function.")
            return func()
        
        print "As the decorator, I return the wrapped function."
        
        return wrapped
    
    print "As a decorator maker, I return a decorator"
    return my_decorator
            
# 我們創(chuàng)建了一個裝飾器俐末。它就只是一個新的函數(shù)料按。
new_decorator = decorator_maker()       
# 輸出為:  
#I make decorators! I am executed only once: when you make me create a decorator.
#As a decorator maker, I return a decorator

# 然后我們裝飾一個函數(shù)       
def decorated_function():
    print "I am the decorated function."
   
decorated_function = new_decorator(decorated_function)
# 輸出為:
#I am a decorator! I am executed only when you decorate a function.
#As the decorator, I return the wrapped function
     
# 我們調(diào)用這個函數(shù):
decorated_function()
# 輸出為:
#I am the wrapper around the decorated function. I am called when you call the decorated function.
#As the wrapper, I return the RESULT of the decorated function.
#I am the decorated function.

這里沒有任何驚奇的地方。

讓我們再次來做相同的事情卓箫,但是省略掉所有討厭的中間變量:

def decorated_function():
    print "I am the decorated function."
decorated_function = decorator_maker()(decorated_function)
# 輸出為:
#I make decorators! I am executed only once: when you make me create a decorator.
#As a decorator maker, I return a decorator
#I am a decorator! I am executed only when you decorate a function.
#As the decorator, I return the wrapped function.

# 最后:
decorated_function()    
# 輸出為:
#I am the wrapper around the decorated function. I am called when you call the decorated function.
#As the wrapper, I return the RESULT of the decorated function.
#I am the decorated function.

讓我們使它更簡潔:

@decorator_maker()
def decorated_function():
    print "I am the decorated function."
# 輸出為:
#I make decorators! I am executed only once: when you make me create a decorator.
#As a decorator maker, I return a decorator
#I am a decorator! I am executed only when you decorate a function.
#As the decorator, I return the wrapped function.

# 最終: 
decorated_function()    
# 輸出為:
#I am the wrapper around the decorated function. I am called when you call the decorated function.
#As the wrapper, I return the RESULT of the decorated function.
#I am the decorated function.

Hey载矿,你注意到了嗎?我們除了 "@"格式的語法糖外還使用了函數(shù)調(diào)用! :-)

因此烹卒,回到帶參數(shù)裝飾器的討論闷盔。如果我們可以使用函數(shù)來創(chuàng)建裝飾器,我們就可以把參數(shù)傳遞給那個函數(shù)旅急,對吧逢勾?

def decorator_maker_with_arguments(decorator_arg1, decorator_arg2):
    
    print "I make decorators! And I accept arguments:", decorator_arg1, decorator_arg2
            
    def my_decorator(func):
        # 這里之所有可以傳遞參數(shù),得益于closures的特性藐吮。  
        # 如果你不熟悉closures,你可以假設(shè)這是沒問題的溺拱,
        # 或者讀一下: http://stackoverflow.com/questions/13857/can-you-explain-closures-as-they-relate-to-python  
        
        print "I am the decorator. Somehow you passed me arguments:", decorator_arg1, decorator_arg2
               
        # 不要把裝飾器的參數(shù)和函數(shù)的參數(shù)搞混
        def wrapped(function_arg1, function_arg2) :
            print ("I am the wrapper around the decorated function.\n"
                  "I can access all the variables\n"
                  "\t- from the decorator: {0} {1}\n"
                  "\t- from the function call: {2} {3}\n"
                  "Then I can pass them to the decorated function"
                  .format(decorator_arg1, decorator_arg2,
                          function_arg1, function_arg2))
            return func(function_arg1, function_arg2)
        
        return wrapped
    
    return my_decorator

@decorator_maker_with_arguments("Leonard", "Sheldon")
def decorated_function_with_arguments(function_arg1, function_arg2):
    print ("I am the decorated function and only knows about my arguments: {0}"
           " {1}".format(function_arg1, function_arg2))
          
decorated_function_with_arguments("Rajesh", "Howard")
# 輸出為:
#I make decorators! And I accept arguments: Leonard Sheldon
#I am the decorator. Somehow you passed me arguments: Leonard Sheldon
#I am the wrapper around the decorated function. 
#I can access all the variables 
#   - from the decorator: Leonard Sheldon 
#   - from the function call: Rajesh Howard 
#Then I can pass them to the decorated function
#I am the decorated function and only knows about my arguments: Rajesh Howard

這就是它了:帶參數(shù)的裝飾器。參數(shù)可以使用變量來設(shè)定

c1 = "Penny"
c2 = "Leslie"

@decorator_maker_with_arguments("Leonard", c1)
def decorated_function_with_arguments(function_arg1, function_arg2):
    print ("I am the decorated function and only knows about my arguments:"
           " {0} {1}".format(function_arg1, function_arg2))

decorated_function_with_arguments(c2, "Howard")
# 輸出為:
#I make decorators! And I accept arguments: Leonard Penny
#I am the decorator. Somehow you passed me arguments: Leonard Penny
#I am the wrapper around the decorated function. 
#I can access all the variables 
#   - from the decorator: Leonard Penny 
#   - from the function call: Leslie Howard 
#Then I can pass them to the decorated function
#I am the decorated function and only knows about my arguments: Leslie Howard

正如你看到的那樣谣辞,你可以使用這個技巧像給函數(shù)傳遞參數(shù)一樣給裝飾器傳遞參數(shù)迫摔。如果你想,你甚至可以使用 *args, **kwargs泥从。但是記住裝飾器只會被調(diào)用一次句占。僅僅當(dāng)Python載入(imports)這個腳本的時候調(diào)用。之后你不可以動態(tài)的設(shè)定參數(shù)躯嫉。當(dāng)你 "import x"的時候, 這個函數(shù)已經(jīng)被裝飾了,因此你不能再改變?nèi)魏螙|西了纱烘。



讓我們來練習(xí)一下:裝飾一個裝飾器

好了,作為福利祈餐,我將給你一個代碼片段,它可以讓裝飾器接收任何參數(shù)擂啥。
為了能接收參數(shù),我們使用另一個函數(shù)來創(chuàng)建我們的裝飾器帆阳。

我們封裝了裝飾器哺壶。

我們最近知道的能封裝函數(shù)的東西是什么?
對,就是裝飾器

讓我們來寫一個裝飾裝飾器的裝飾器來玩玩(夠繞吧):

def decorator_with_args(decorator_to_enhance): 
    """  
    這個函數(shù)被當(dāng)做裝飾器來使用变骡。  
    它必須裝飾另一個函數(shù)离赫,這個函數(shù)也被當(dāng)做裝飾器使用
    感覺理解不過來,休息一下
    它允許任何的裝飾器接收任何參數(shù)塌碌。
    在你的腦子里面記住每次應(yīng)該怎樣構(gòu)建這樣的裝飾器
    """
    
    # 我們使用相同的技巧來傳遞參數(shù)
    def decorator_maker(*args, **kwargs):
        
        # 我們動態(tài)的創(chuàng)建一個接收一個函數(shù)作為參數(shù)的裝飾器  
        # 保持住從decorator_maker傳遞過來的參數(shù)渊胸。
        def decorator_wrapper(func):
       
            # 我們最后返回原始裝飾器的結(jié)果,  
            # 這個結(jié)果就是最原始被裝飾的函數(shù) (就是返回一個函數(shù))台妆。
            # 這里只有一個小缺陷:被封裝的裝飾器必須具有特定的簽名翎猛,不然它不會工作
            return decorator_to_enhance(func, *args, **kwargs)這個樣子
        
        return decorator_wrapper
    
    return decorator_maker

它可以像下面這樣使用:

# 你創(chuàng)建一個將要被用作裝飾器的函數(shù)。并向它添加一個裝飾器(這里是指decorator_with_args) :-)
# 不要忘了你創(chuàng)建的這個裝飾器簽名必須是 "decorator(func, *args, **kwargs)"
@decorator_with_args 
def decorated_decorator(func, *args, **kwargs): 
    def wrapper(function_arg1, function_arg2):
        print "Decorated with", args, kwargs
        return func(function_arg1, function_arg2)
    return wrapper
    
# 之后你使用上面定義的裝飾器(指decorated_decorator)來裝飾一個函數(shù)
@decorated_decorator(42, 404, 1024)
def decorated_function(function_arg1, function_arg2):
    print "Hello", function_arg1, function_arg2

decorated_function("Universe and", "everything")
# 輸出為:
#Decorated with (42, 404, 1024) {}
#Hello Universe and everything

# 喔!

我知道接剩,你有了這樣一種感覺切厘,它是在聽一一個家伙說:"before understanding recursion, you must first understand recursion"產(chǎn)生的。但是懊缺,現(xiàn)在疫稿,你是否有更好的體會了?


最佳實踐: 裝飾器

  • 裝飾器是Python2.4引入的鹃两,因此保證你的代碼運行的版本 >= 2.4遗座。
  • 裝飾器會拖慢函數(shù)的執(zhí)行速度,記住這點。
  • 你不能反裝飾一個函數(shù)俊扳。因此一旦一個函數(shù)被裝飾了途蒋,它對所有其它代碼來說就都是被裝飾了的。
  • 裝飾器會封裝函數(shù)馋记,這會讓它們變得更難調(diào)試号坡。 (這點從Python >= 2.5版本變得好了起來; 具體參考下面。)

在Python 2.5中 functools模塊被引入梯醒。它包含了 functools.wraps()函數(shù)宽堆,這個函數(shù)會將被裝飾函數(shù)的名稱、模塊冤馏、文檔字符串拷貝到封裝函數(shù)日麸。

(有趣的事實是: functools.wraps() 也是一個裝飾器! ?)

# 為了調(diào)試寄啼,在堆棧軌跡中打印了函數(shù)的名稱(__name__)
def foo():
    print "foo"
    
print foo.__name__
# 輸出為 : foo
    
# 有了裝飾器逮光,名稱就凌亂了 
def bar(func):
    def wrapper():
        print "bar"
        return func()
    return wrapper

@bar
def foo():
    print "foo"

print foo.__name__
# 輸出為: wrapper

# "functools"對這個有幫助

import functools

def bar(func):
    # 我們看到"wrapper"封裝了"func"
    # 現(xiàn)在魔法開始了
    @functools.wraps(func)
    def wrapper():
        print "bar"
        return func()
    return wrapper

@bar
def foo():
    print "foo"

print foo.__name__
# 輸出為: foo

裝飾器可以被用在什么地方?

現(xiàn)在問題來了:我可以使用裝飾器來干什么?

盡管看起來很酷很強(qiáng)大,但是一個實際的應(yīng)用事例能更好的說明問題墩划。好了涕刚,這里有1000種可能性。經(jīng)典的使用是在庫以外的代碼中擴(kuò)展一個函數(shù)的行為 (你不能修改的函數(shù))乙帮,或者只是為了調(diào)試 (因為調(diào)試只是零時的杜漠,所有你不想修改這個函數(shù))。

你可以以一種DRY的方式使用他們來擴(kuò)展許多函數(shù),像這樣:

def benchmark(func):
    """
    打印原函數(shù)調(diào)用時間的裝飾器
    """
    import time
    def wrapper(*args, **kwargs):
        t = time.clock()
        res = func(*args, **kwargs)
        print func.__name__, time.clock()-t
        return res
    return wrapper


def logging(func):
    """
    記錄腳本行為日志的裝飾器  
    (這里只是打印它驾茴,但是也可以記錄到日志里面盼樟!)
    """
    def wrapper(*args, **kwargs):
        res = func(*args, **kwargs)
        print func.__name__, args, kwargs
        return res
    return wrapper


def counter(func):
    """
    記錄并打印一個函數(shù)執(zhí)行次數(shù)的裝飾器
    """
    def wrapper(*args, **kwargs):
        wrapper.count = wrapper.count + 1
        res = func(*args, **kwargs)
        print "{0} has been used: {1}x".format(func.__name__, wrapper.count)
        return res
    wrapper.count = 0
    return wrapper

@counter
@benchmark
@logging
def reverse_string(string):
    return str(reversed(string))

print reverse_string("Able was I ere I saw Elba")
print reverse_string("A man, a plan, a canoe, pasta, heros, rajahs, a coloratura, maps, snipe, percale, macaroni, a gag, a banana bag, a tan, a tag, a banana bag again (or a camel), a crepe, pins, Spam, a rut, a Rolo, cash, a jar, sore hats, a peon, a canal: Panama!")

#輸出為:
#reverse_string ('Able was I ere I saw Elba',) {}
#wrapper 0.0
#wrapper has been used: 1x 
#ablE was I ere I saw elbA
#reverse_string ('A man, a plan, a canoe, pasta, heros, rajahs, a coloratura, maps, snipe, percale, macaroni, a gag, a banana bag, a tan, a tag, a banana bag again (or a camel), a crepe, pins, Spam, a rut, a Rolo, cash, a jar, sore hats, a peon, a canal: Panama!',) {}
#wrapper 0.0
#wrapper has been used: 2x
#!amanaP :lanac a ,noep a ,stah eros ,raj a ,hsac ,oloR a ,tur a ,mapS ,snip ,eperc a ,)lemac a ro( niaga gab ananab a ,gat a ,nat a ,gab ananab a ,gag a ,inoracam ,elacrep ,epins ,spam ,arutaroloc a ,shajar ,soreh ,atsap ,eonac a ,nalp a ,nam A

當(dāng)然,裝飾器最好的一點是你不需要重寫锈至,就可以幾乎在任何東西上面使用它們晨缴。這就是我所說的,DRY:

@counter
@benchmark
@logging
def get_random_futurama_quote():
    from urllib import urlopen
    result = urlopen("http://subfusion.net/cgi-bin/quote.pl?quote=futurama").read()
    try:
        value = result.split("<br><b><hr><br>")[1].split("<br><br><hr>")[0]
        return value.strip()
    except:
        return "No, I'm ... doesn't!"

    
print get_random_futurama_quote()
print get_random_futurama_quote()

#outputs:
#get_random_futurama_quote () {}
#wrapper 0.02
#wrapper has been used: 1x
#The laws of science be a harsh mistress.
#get_random_futurama_quote () {}
#wrapper 0.01
#wrapper has been used: 2x
#Curse you, merciful Poseidon!

Python語言本身提供了好多種裝飾器:property, staticmethod,等等峡捡。

  • Django使用裝飾器來管理緩存和視圖權(quán)限击碗。
  • Twisted to fake inlining asynchronous functions calls.(對Twisted不熟,不知道具體講的是神馬们拙!)稍途。

這個真是一個巨大的游樂場

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市砚婆,隨后出現(xiàn)的幾起案子械拍,更是在濱河造成了極大的恐慌,老刑警劉巖装盯,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件殊者,死亡現(xiàn)場離奇詭異,居然都是意外死亡验夯,警方通過查閱死者的電腦和手機(jī)猖吴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來挥转,“玉大人海蔽,你說我怎么就攤上這事“笠ィ” “怎么了党窜?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長借宵。 經(jīng)常有香客問我幌衣,道長,這世上最難降的妖魔是什么壤玫? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任豁护,我火速辦了婚禮,結(jié)果婚禮上欲间,老公的妹妹穿的比我還像新娘楚里。我一直安慰自己,他們只是感情好猎贴,可當(dāng)我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布班缎。 她就那樣靜靜地躺著蝴光,像睡著了一般。 火紅的嫁衣襯著肌膚如雪达址。 梳的紋絲不亂的頭發(fā)上蔑祟,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機(jī)與錄音沉唠,去河邊找鬼做瞪。 笑死,一個胖子當(dāng)著我的面吹牛右冻,可吹牛的內(nèi)容都是我干的装蓬。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼纱扭,長吁一口氣:“原來是場噩夢啊……” “哼牍帚!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起乳蛾,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤暗赶,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后肃叶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蹂随,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年因惭,在試婚紗的時候發(fā)現(xiàn)自己被綠了岳锁。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡蹦魔,死狀恐怖激率,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情勿决,我是刑警寧澤乒躺,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站低缩,受9級特大地震影響嘉冒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜咆繁,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一讳推、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧么介,春花似錦娜遵、人聲如沸蜕衡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至久脯,卻和暖如春纳胧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背帘撰。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工跑慕, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人摧找。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓核行,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蹬耘。 傳聞我的和親對象是個殘疾皇子芝雪,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 要點: 函數(shù)式編程:注意不是“函數(shù)編程”,多了一個“式” 模塊:如何使用模塊 面向?qū)ο缶幊蹋好嫦驅(qū)ο蟮母拍钭厶Α傩浴?..
    victorsungo閱讀 1,468評論 0 6
  • Python進(jìn)階框架 希望大家喜歡惩系,點贊哦首先感謝廖雪峰老師對于該課程的講解 一、函數(shù)式編程 1.1 函數(shù)式編程簡...
    Gaolex閱讀 5,490評論 6 53
  • http://python.jobbole.com/85231/ 關(guān)于專業(yè)技能寫完項目接著寫寫一名3年工作經(jīng)驗的J...
    燕京博士閱讀 7,548評論 1 118
  • 前言 Python的修飾器的英文名叫Decorator如筛,當(dāng)你看到這個英文名的時候堡牡,你可能會把其跟Design Pa...
    linheimx閱讀 627評論 0 4
  • 零碎記錄。 2017年8月26日星期六 昨天辦理了出院手續(xù)杨刨,身體不適的癥狀漸漸好起來晤柄。這段時間自己懶惰了,雖然也想...
    若蘭幽香閱讀 210評論 0 2