python的裝飾器

因爲函數也是一個對象,並且函數做爲一個特殊的對象能夠被賦值給其餘變量(value = pringname()),相反,經過變量也是能夠調用函數的,如下是一個簡單的例子:app

1 >>> def printName():
2 ... print("My name is Jobs")
3 ... 
4 >>> name = printName
5 >>> 
6 >>> printName()
7 My name is Jobs
8 >>> name()
9 My name is Jobs

 


由以上代碼咱們不難看出,printName()函數被賦值給name變量,一樣用name變量也能夠調用printName()函數。此時他們是相同的,在這裏說一點小知識:函數對象有函數

一個__name__屬性,能夠拿到函數的名字:spa

例子:日誌

1 >>> 
2 >>> name.__name__
3 'printName'
4 >>> printName.__name__
5 'printName'
6 
7 

如今,假設咱們要加強printName函數的功能,好比,在函數調用先後自動打印日誌,但又不但願修改printName函數的定義,這種在代碼運行期間動態增長功能的方式,稱之爲「code

裝飾器」(Decorator)。本質上,decorator就是一個返回函數的高階函數。因此,咱們要定義一個能打印日誌的decorator,能夠定義以下:對象

例子一:blog

 

1 >>> 
2 >>> def log(func):
3 ... def wrapper(*args,**kw):
4 ... print 'call %s()' %func.__name__
5 ... return func(*args,**kw)
6 ... return wrapper
7 ...

 

 

觀察上面的log,由於它是一個decorator,因此接受一個函數做爲參數,並返回一個函數。咱們要藉助Python的@語法,把decorator置於函數的定義處:class

1 >>> @log
2 ... def printName():
3 ... print("My name is Jobs")
4 ... 
5 >>> printName()
6 call printName()
7 My name is Jobs

例子二:import

 1 >>> 
 2 >>> @log
 3 ... def printAge():
 4 ... print '%s,my age is 120' %printName() 
 5 ... 
 6 >>> job = printAge()
 7 call printAge()
 8 call printName()
 9 My name is Jobs
10 None,my age is 120

 

當你調用printName或者printAge()時,不只會調用函數自己,並且會調用在調用該函數以前打印一些日誌。把@log放在printAge()函數以前至關於執行了語句:變量

name= log(printName)

因爲log()是一個decorator,返回一個函數,因此,原來的now()函數仍然存在,只是如今同名的printName()了新的函數,因而調用printName()將執行新函數,即在log()函數中返回的wrapper()函數。

wrapper()函數的參數定義是(*args, **kw),所以,wrapper()函數能夠接受任意參數的調用。在wrapper()函數內,首先打印日誌,再緊接着調用原始函數。

若是decorator自己須要傳入參數,那就須要編寫一個返回decorator的高階函數,寫出來會更復雜。好比,要自定義log的文本:

1 def log(text):
2     def decorator(func):
3         def wrapper(*args, **kw):
4             print '%s %s():' % (text, func.__name__)
5             return func(*args, **kw)
6         return wrapper
7     return decorator

這個3層嵌套

 1 >>> def log(text):
 2 ...     def decorator(func):
 3 ...         def wrapper(*args,**kw):
 4 ...             print '%s %s():' %(text,func.__name__)
 5 ...             return func(*args,**kw)
 6 ...         return wrapper
 7 ...     return decorator
 8 ... 
 9 >>> @log('excute')
10 ... def printName():
11 ...     print("My name is Jobs!")
12 ... 
13 >>> printName()
14 excute printName():
15 My name is Jobs!
16 >>> 

咱們來剖析上面的語句,首先執行log('execute'),返回的是decorator函數,再調用返回的函數,參數是now函數,返回值最終是wrapper函數。

以上兩種decorator的定義都沒有問題,但還差最後一步。由於咱們講了函數也是對象,它有__name__等屬性,但你去看通過decorator裝飾以後的函數,它們的__name__已經從原來的'printName變成了'wrapper'

>>> printName.__name__
'wrapper'
>>> 

由於返回的那個wrapper()函數名字就是'wrapper',因此,須要把原始函數的__name__等屬性複製到wrapper()函數中,不然,有些依賴函數簽名的代碼執行就會出錯。

不須要編寫wrapper.__name__ = func.__name__這樣的代碼,Python內置的functools.wraps就是幹這個事的,因此,一個完整的decorator的寫法以下:

1 >>> def log(func):
2 ...     @functools.wraps(func)
3 ...     def wrapper(*args,**kw):
4 ...         print 'call %s():' %func.__name__
5 ...         return func(*args,**kw)
6 ...     return wrapper
7 ... 
8 >>> 

也能夠針對呆參數的decorator:

import functools

def log(text):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print '%s %s():' % (text, func.__name__)
            return func(*args, **kw)
        return wrapper
    return decorator

 

這是一個更新的例子,但願幫助你們理解:

 1 >>> def deco(func):
 2 ...     def _deco(a,b):
 3 ...         print ("before myfunc() called.")
 4 ...         ret = func(a,b)
 5 ...         print("after called.result:%s" %ret)
 6 ...         return ret
 7 ...     return _deco
 8 ... 
 9 
10 >>> @deco
11 ... def myfunc(a,b):
12 ...     print("myfunc(%s,%s) called."%(a,b))
13 ...     return a+b
14 ... 
15 >>> myfunc(1,2)
16 before myfunc() called.  
17 myfunc(1,2) called.
18 after called.result:3
19 3
20 >>> 

 

補充說明:

  其實,裝飾器就是一個函數,一個能夠用來包裝函數的函數,最後返回一個修改以後的函數(這裏的修改,好比增長日誌,如上例。)將其從新賦值原來的標識符,並永久喪失對原始函數對象的訪問。

相關文章
相關標籤/搜索