轉載:唐磊的我的博客《python中decorator詳解》【轉註:深刻淺出清晰明瞭】

轉載請註明來源:唐磊的我的博客《python中decorator詳解》python

前面寫python的AOP解決方案時提到了decorator,這篇文章就詳細的來整理下python的裝飾器——decorator。web

python中的函數即objects

一步一步來,先了解下python中的函數。c#

def shout(word='hello,world'):
    return word.capitalize() + '!'print shout()#輸出:Hello,world!#跟其餘對象同樣,你一樣能夠將函數對象賦值給其餘變量scream = shout#注意,shout後面沒有用括號(),用括號就是調用了shout方法,這裏僅僅是將shout的函數對象複製給scream。#意味着,你能夠經過scream調用shout方法。print scream()#一樣輸出:Hello,world!#此外,你將表明函數變量的shout刪除,shout如今失效,但scream一樣能夠調用del shoutprint shout()#會拋異常#Traceback (most recent call last):#  File "S:\decorator.py", line 18, in #    print shout()#NameError: name 'shout' is not defined#(固然得註釋掉前面的兩句代碼,或者try catch一下)print scream()#一樣輸出:Hello,world!

python中函數還能夠嵌套,即將函數定義在另外一個函數裏面。例如設計模式

def talk():
    
    def whisper(word='hello'):
        return word.lower() + '...'

    #在函數talk裏面定義whisper函數,立刻就能夠用了
    print whisper()#調用talk,裏面的whisper()也被調用了talk()#輸出:hello...#直接調用whisper可不行。try:    print whisper()except Exception ,e :    print e#輸出:name 'whisper' is not defined

從上面的例子咱們已經能夠獲得,函數能夠被賦值給另外的變量,也能在另外一個函數裏面定義函數。能夠推斷函數也能返回給另外的函數或者做爲參數傳遞給其餘函數。看下面的代碼:api

def getTalk(type='shout'):
    #函數裏面定義另外兩個函數
    def shout(word='hello,world'):
        return word.capitalize() + '!'
    def whisper(word='hello'):
        return word.lower() + '...'

    #根據傳入的參數,返回相應的函數對象
    #注意,沒用(),僅僅返回函數對象,並無調用
    if type == 'shout':        return shout    else:        return whisper

talk = getTalk()print talk#輸出:print talk() #調用函數#輸出:Hello,world!#一樣能夠這樣直接調用print getTalk('whisper')()#輸出:hello...#既然能return一個函數,固然也能夠將函數做爲參數傳遞給其餘的函數def doBefore(func):
    print 'before calling the func'
    print func()#調用doBefore(talk)#將前面經過getTalk()獲得的函數對象傳遞給doBefore#輸出:#before calling the func#Hello,world!

 

初識decorator

明白以上的這些,將有助於理解裝飾器decorator,其實裝飾器decorator就是在不改變函數的自己狀況下在函數執行的先後包裝另外的代碼來改變函數的具體表現行爲。有點兒AOP的味道。繼續從代碼來解釋。緩存

# 裝飾器就是一個將其餘函數(也就是被裝飾的函數)做爲參數的一個functiondef my_shiny_new_decorator(a_function_to_decorate):

    # 在這個裝飾器函數裏面用另一個函數來包裝原函數,
    # 即在調用原來的函數先後分別執行其餘的操做.
    def the_wrapper_around_the_original_function():

        # 這裏放一些你但願在原來的函數執行以前的代碼,例如log、身份驗證等
        print "Before the function runs"

        # 調用原函數自己,注意有()
        a_function_to_decorate()        #同理,這裏放一些你但願在原來的函數執行以後的代碼,例如釋放資源等
        print "After the function runs"

    # 注意,此時,函數 "a_function_to_decorate"並無被執行.
    # 此時將剛剛建立的用來包裝原函數的函數返回
    return the_wrapper_around_the_original_function# 假設你定義了一個函數,並良好實現,也以爲perfect了。且發誓之後並不許備改動這段代碼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# 有時候,做爲coder,並非你不動代碼就能不動的。PM此時另外又來了個需求,讓你在以前#的基礎上再加點XX功能,苦逼的程序猿乖乖的改吧。不過有了裝飾器,你能知足PM的需求,又能不違背#當初許下的不改變原有代碼的誓言。僅僅須要將此函數拿去從新裝飾一下便可。以下: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_decorated()這個方法,仍是鍾情於之前的那個名字,方法也簡單。重寫下a_stand_alone_function方法便可。即:app

a_stand_alone_function = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function()#輸出仍是同樣樣

decorator揭祕

上面的代碼就是裝飾器的體現。如今用python中專門的decorator語法就是醬紫滴:函數

@my_shiny_new_decoratordef another_stand_alone_function():
    print "Leave me alone"another_stand_alone_function()#Before the function runs#Leave me alone#After the function runs

簡單來講,@my_shiny_new_decorator就是another_stand_alone_function = my_shiny_new_decorator(another_stand_alone_function)的縮寫。spa

python中的裝飾器實際上是GoF中的裝飾模式的一個變種。像迭代器iterators也是一種設計模式。debug

裝飾器還能夠同時使用多個。

def bread(func):
    def wrapper():
        print ""
        func()        print "<\______/>"
    return wrapperdef ingredients(func):
    def wrapper():
        print "#tomatoes#"
        func()        print "~salad~"
    return wrapperdef sandwich(food="--ham--"):
    print food

sandwich()#輸出: --ham--sandwich = bread(ingredients(sandwich))
sandwich()#輸出:## #tomatoes## --ham--# ~salad~#<\______/>#用python中的decorator語法就是:@bread@ingredientsdef sandwich(food="--ham--"):
    print food

sandwich()#輸出:## #tomatoes## --ham--# ~salad~#<\______/>#注意@的順序。誰在最下面,睡最早被調用。#下面的sandwich就奇怪了哈,把salad等放在盤子底下@ingredients@breaddef strange_sandwich(food="--ham--"):
    print food

strange_sandwich()#輸出:##tomatoes### --ham--#<\______/># ~salad~

被裝飾的函數傳參

僅僅在包裝的函數上將參數傳遞過來便可。

def a_decorator_passing_arguments(function_to_decorate):
    def a_wrapper_accepting_arguments(arg1, arg2):
        print "傳進來的參數是:", arg1, arg2
        function_to_decorate(arg1, arg2)    return a_wrapper_accepting_arguments# 既然你調用了裝飾事後的函數,你也就調用了包裝器,函數也就天然傳遞到了包裝器。@a_decorator_passing_argumentsdef print_full_name(first_name, last_name):
    print "My name is", first_name, last_name

print_full_name("lei", "tang")# 輸出:#傳進來的參數是: lei tang#My name is lei tang

裝飾類中的方法

python中的方法和函數差很少,只不過類中的方法的第一個參數是當前對象的引用self。

def method_friendly_decorator(method_to_decorate):
    def wrapper(self, lie):
        lie = lie - 3 #減小3
        return method_to_decorate(self, lie)    return wrapperclass 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)#32-3  -3(wrapper中還減了3歲)=26#輸出: I am 26, what did you think?

在不知道參數個數的狀況下有一種更加通用的傳參方法,就是用*args, **kwargs。*args表明沒有給定默認值的參數列表(arg1,arg2,……),**kwwars表明有給定默認值(arg1=val1,arg2=val2,……)

def a_decorator_passing_arbitrary_arguments(function_to_decorate):
    # 包裝器接受任意參數
    def a_wrapper_accepting_arbitrary_arguments(*args, **kwargs):
        print "Do I have args?:"
        print args        print kwargs        #*args表明沒有給定默認值的參數列表(arg1,arg2,……),**kwwars表明有給定默認值(arg1=val1,arg2=val2,……)
        function_to_decorate(*args, **kwargs)    return a_wrapper_accepting_arbitrary_arguments@a_decorator_passing_arbitrary_argumentsdef 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_argumentsdef 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_argumentsdef 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): # You can now add a default value
        print "I am %s, what did you think ?" % (self.age + lie)

m = Mary()
m.sayYourAge(8)
m.sayYourAge(lie=-8)#注意看下面print出的**kwargs區別#輸出#Do I have args?:#(<__main__.Mary object at 0x01228430>, 8)#{}#I am 39, what did you think ?#Do I have args?:#(<__main__.Mary object at 0x01228430>,)#{'lie': -8}#I am 23, what did you think ?

將參數傳給裝飾函數

由於裝飾函數自己要將一個函數做爲參數傳遞進來,那怎麼把參數傳給裝飾函數呢?其實不能直接將函數傳遞給裝飾函數。但也有解決方案,先看下面的例子

# 裝飾器函數也是functiondef my_decorator(func):
    print "I am a ordinary function"
    def wrapper():
        print "I am function returned by the decorator"
        func()    return wrapperdef lazy_function():
    print "zzzzzzzz"decorated_function = my_decorator(lazy_function)#輸出: I am a ordinary function#注意此時,被裝飾的函數並無調用。#一樣,用@的方式也獲得相同的結果@my_decoratordef lazy_function():
    print "zzzzzzzz"#輸出:  I am a ordinary function#當@的時候,就告訴python去調用裝飾器函數。這個@後的標籤很重要,直接指向裝飾器
def decorator_maker():

    print "我是構造裝飾器的工人,當你讓我構造裝飾器時,我被執行一次 "

    def my_decorator(func):

        print "我是裝飾器,當你裝飾一個函數式,我就被執行"

        def wrapped():
            print ("我是包裝被裝飾函數的包裝器,當被裝飾函數調用時,我也被執行,"
                  "做爲包裝器,個人職責是將被裝飾的函數結果返回")            return func()        print "做爲裝飾器,我將返回包裝器函數"

        return wrapped    print "做爲構造裝飾器的工人,我得返回裝飾器"
    return my_decorator# 構造裝飾器,賦值給new_decoratornew_decorator = decorator_maker()       
#輸出:#我是構造裝飾器的工人,當你讓我構造裝飾器時,我被執行一次 #做爲構造裝飾器的工人,我得返回裝飾器print(0)# 被裝飾的函數def decorated_function():
    print "我是被裝飾的函數"decorated_function = new_decorator(decorated_function)#輸出:#我是裝飾器,當你裝飾一個函數式,我就被執行#做爲裝飾器,我將返回包裝器函數print(1)#調用被裝飾的函數decorated_function()#輸出:#我是包裝被裝飾函數的包裝器,當被裝飾函數調用時,我也被執行做爲包裝器,個人職責是將被裝飾的函數結果返回#我是被裝飾的函數

用標記間隔下,整個輸出以下:

我是構造裝飾器的工人,當你讓我構造裝飾器時,我被執行一次
做爲構造裝飾器的工人,我得返回裝飾器0我是裝飾器,當你裝飾一個函數式,我就被執行
做爲裝飾器,我將返回包裝器函數1我是包裝被裝飾函數的包裝器,當被裝飾函數調用時,我也被執行,做爲包裝器,個人職責是將被裝飾的函數結果返回
我是被裝飾的函數

省略中間步驟,以下

#省略中間步驟,以下def decorated_function():
    print "我是被裝飾的函數"decorated_function = decorator_maker()(decorated_function)#我是構造裝飾器的工人,當你讓我構造裝飾器時,我被執行一次 #做爲構造裝飾器的工人,我得返回裝飾器#我是裝飾器,當你裝飾一個函數式,我就被執行#做爲裝飾器,我將返回包裝器函數# 最後調用:decorated_function()    
#輸出:#我是包裝被裝飾函數的包裝器,當被裝飾函數調用時,我也被執行,做爲包裝器,個人職責是將被裝飾的函數結果返回#我是被裝飾的函數

能夠更短

@decorator_maker()def decorated_function():
    print "我是被裝飾的函數"#輸出:#我是構造裝飾器的工人,當你讓我構造裝飾器時,我被執行一次 #我是裝飾器,當你裝飾一個函數式,我就被執行#做爲裝飾器,我將返回包裝器函數#最終: decorated_function()    
#輸出:#我是包裝被裝飾函數的包裝器,當被裝飾函數調用時,我也被執行,做爲包裝器,個人職責是將被裝飾的函數結果返回#我是被裝飾的函數

上面@後面是一個函數調用,調用函數固然能夠傳遞參數。就是在上面的@decorator_maker()括號裏面加上參數而已。

# -*- coding: utf-8 -*-def decorator_maker_with_arguments(decorator_arg1, decorator_arg2):

    print "我構造裝飾器,並接受參數:", decorator_arg1, decorator_arg2    def my_decorator(func):
        
        print "我是裝飾器,我接收到了參數:", decorator_arg1, decorator_arg2        # 別混淆了,裝飾器參數和函數參數
        def wrapped(function_arg1, function_arg2) :
            print ("我是包裝器,我能看到全部的變量.\n"
                  "\t- 來自裝飾器: {0} {1}\n"
                  "\t- 來自函數調用: {2} {3}\n"
                  "我能夠傳遞給被裝飾的函數"
                  .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 ("被裝飾的函數,我只能看到本身的參數: {0}"
           " {1}".format(function_arg1, function_arg2))

decorated_function_with_arguments("Lily", "Lucy")#輸出#我構造裝飾器,並接受參數: Leonard Sheldon#我是裝飾器,我接收到了參數: Leonard Sheldon#我是包裝器,我能看到全部的變量.#	- 來自裝飾器: Leonard Sheldon#	- 來自函數調用: Lily Lucy#我能夠傳遞給被裝飾的函數#被裝飾的函數,我只能看到本身的參數: Lily Lucyc1 = "Penny"c2 = "Pony"@decorator_maker_with_arguments("Leonard", c1)def decorated_function_with_arguments(function_arg1, function_arg2):
    print ("被裝飾的函數,我只能看到本身的參數: {0} {1}".format(function_arg1, function_arg2))

decorated_function_with_arguments(c2, "Lucy")#我構造裝飾器,並接受參數: Leonard Penny#我是裝飾器,我接收到了參數: Leonard Penny#我是包裝器,我能看到全部的變量.#	- 來自裝飾器: Leonard Penny#	- 來自函數調用: Pony Lucy#我能夠傳遞給被裝飾的函數#被裝飾的函數,我只能看到本身的參數: Pony Lucy

經過這種方案就能夠實現往裝飾器裏面傳遞參數了,甚至能夠用上面的*args, **kwargs來傳遞任意參數。值得注意的是裝飾器只被調用了一次,當python在import當前腳本的時候調用。不能在後面再動態的設置傳遞的參數,當import xx的時候,被decorated的函數已經被裝飾好了,不能再變了。

decorator注意事項

  • python2.4及其以上版本有此功能

  • 裝飾器使代碼執行的效率變低了

  • 一旦一個函數已經被裝飾好了,就定下來了,不能變了

  • 裝飾器decorator在函數之間包裝起來了,比較難debug

python本身也提供了幾個裝飾器,property,staticmethod等。Django用裝飾器來管理緩存和視圖權限,可以把python的decorator玩透,將會幫你解決不少事情。

相關文章
相關標籤/搜索