[轉] Python中的裝飾器(decorator)

想理解Python的decorator首先要知道在Python中函數也是一個對象,因此你能夠html

  • 將函數複製給變量
  • 將函數當作參數
  • 返回一個函數

函數在Python中和變量的用法同樣也是一等公民,也就是高階函數(High Order Function)。全部的魔法都是由此而來。python

1,起源

咱們想在函數login中輸出調試信息,咱們能夠這樣作app

def login():
    print('in login')
 
def printdebug(func):
    print('enter the login')
    func()
    print('exit the login')
 
printdebug(login)

這個方法討厭的是每次調用login是,都經過printdebug來調用,但畢竟這是可行的。函數

2,讓代碼變得優美一點

既然函數能夠做爲返回值,能夠賦值給變量,咱們可讓代碼優美一點。性能

def login():
    print('in login')
 
def printdebug(func):
    def __decorator():
        print('enter the login')
        func()
        print('exit the login')
    return __decorator  #function as return value
 
debug_login = printdebug(login)  #function assign to variable
 
debug_login()  #execute the returned function

這樣咱們每次只要調用debug_login就能夠了,這個名字更符合直覺。咱們將原先的兩個函數printdebug和login綁定到一塊兒,成爲debug_login。這種耦合叫內聚:-)。學習

3,讓代碼再優美一點

printdebug和login是經過debug_login = printdebug(login)這一句來結合的,這一句彷佛也是多餘的,能不能在定義login是加個標註,從而將printdebug和login結合起來?ui

上面的代碼從語句組織的角度來說很難再優美了,Python的解決方案是提供一個語法糖(Syntax Sugar),用一個@符號來結合它們。this

def printdebug(func):
    def __decorator():
        print('enter the login')
        func()
        print('exit the login')
    return __decorator 
 
@printdebug  #combine the printdebug and login
def login():
    print('in login')
 
login()  #make the calling point more intuitive

能夠看出decorator就是一個:使用函數做參數而且返回函數的函數。經過改進咱們能夠獲得:spa

  • 更簡短的代碼,將結合點放在函數定義時
  • 不改變原函數的函數名

在Python解釋器發現login調用時,他會將login轉換爲printdebug(login)()。也就是說真正執行的是__decorator這個函數。debug

4,加上參數

1,login函數帶參數

login函數可能有參數,好比login的時候傳人user的信息。也就是說,咱們要這樣調用login:

login(user)

Python會將login的參數直接傳給__decorator這個函數。咱們能夠直接在__decorator中使用user變量:

def printdebug(func):
    def __decorator(user):    #add parameter receive the user information
        print('enter the login')
        func(user)  #pass user to login
        print('exit the login')
    return __decorator 
 
@printdebug 
def login(user):
    print('in login:' + user)
 
login('jatsz')  #arguments:jatsz

咱們來解釋一下login(‘jatsz’)的調用過程:

[decorated] login(‘jatsz’)   =>   printdebug(login)(‘jatsz’)  =>  __decorator(‘jatsz’)  =>  [real] login(‘jatsz’)

2,裝飾器自己有參數

咱們在定義decorator時,也能夠帶入參數,好比咱們這樣使用decorator,咱們傳入一個參數來指定debug level。

@printdebug(level=5)
def login
    pass

爲了接收decorator傳來的參數,咱們在本來的decorator上再包裝一個函數來接收參數:

def printdebug_level(level):  #add wrapper to recevie decorator's parameter
    def printdebug(func):
        def __decorator(user):   
            print('enter the login, and debug level is: ' + str(level)) #print debug level
            func(user) 
            print('exit the login')
        return __decorator 
    return printdebug    #return original decorator
 
@printdebug_level(level=5)   #decorator's parameter, debug level set to 5
def login(user):
    print('in login:' + user)
 
login('jatsz')

咱們再來解釋一下login(‘jatsz’)整個調用過程:

[decorated]login(‘jatsz’) => printdebug_level(5) => printdebug[with closure value 5](login)(‘jatsz’) => __decorator(‘jatsz’)[use value 5]  => [real]login(‘jatsz’)

 

5,裝飾有返回值的函數

有時候login會有返回值,好比返回message來代表login是否成功。

1
login_result =  login(‘jatsz’)

咱們須要將返回值在decorator和調用函數間傳遞:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def  printdebug(func):
     def  __decorator(user):   
         print ( 'enter the login' )
         result =  func(user)  #recevie the native function call result
         print ( 'exit the login' )
         return  result        #return to caller
     return  __decorator 
 
@printdebug 
def  login(user):
     print ( 'in login:'  +  user)
     msg =  "success"  if  user = =  "jatsz"  else  "fail"
     return  msg  #login with a return value
 
result1 =  login( 'jatsz' );
print  result1  #print login result
 
result2 =  login( 'candy' );
print  result2

咱們解釋一下返回值的傳遞過程:

...omit for brief…[real][msg from login(‘jatsz’) => [result from]__decorator => [assign to] result1

6,應用多個裝飾器

咱們能夠對一個函數應用多個裝飾器,這時咱們須要留心的是應用裝飾器的順序對結果會產生。影響好比:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def  printdebug(func):
     def  __decorator():   
         print ( 'enter the login' )
         func()
         print ( 'exit the login' )
     return  __decorator 
 
def  others(func):    #define a other decorator
     def  __decorator():
         print  '***other decorator***'
         func()
     return  __decorator
 
@others          #apply two of decorator
@printdebug
def  login():
     print ( 'in login:' )
 
@printdebug     #switch decorator order
@others
def  logout():
     print ( 'in logout:' )
 
login()
print ( '---------------------------' )
logout()

咱們定義了另外一個裝飾器others,而後咱們對login函數和logout函數分別應用這兩個裝飾器。應用方式很簡單,在函數定義是直接用兩個@@就能夠了。咱們看一下上面代碼的輸出:

1
2
3
4
5
6
7
8
9
10
$ python deoc.py
***other decorator***
enter the login
in login:
exit the login
---------------------------
enter the login
***other decorator***
in logout:
exit the login

咱們看到兩個裝飾器都已經成功應用上去了,不過輸出卻不相同。形成這個輸出不一樣的緣由是咱們應用裝飾器的順序不一樣。回頭看看咱們login的定義,咱們是先應用others,而後纔是printdebug。而logout函數真好相反,發生了什麼?若是你仔細看logout函數的輸出結果,能夠看到裝飾器的遞歸。從輸出能夠看出:logout函數先應用printdebug,打印出「enter the login」。printdebug的__decorator調用中間應用了others的__decorator,打印出「***other decorator***」。其實在邏輯上咱們能夠將logout函數應用裝飾器的過程這樣看(僞代碼):

1
2
3
4
5
6
7
8
@printdebug     #switch decorator order
(
     @others
     (
         def  logout():
             print ( 'in logout:' )
     )
)

咱們解釋一下整個遞歸應用decorator的過程:

[printdebug decorated]logout() =>

printdebug.__decorator[call [others decorated]logout() ] =>

printdebug.__decorator.other.__decorator[call real logout]

 

7,靈活運用

什麼狀況下裝飾器不適用?裝飾器不能對函數的一部分應用,只能做用於整個函數。

login函數是一個總體,當咱們想對部分函數應用裝飾器時,裝飾器變的無從下手。好比咱們想對下面這行語句應用裝飾器:

1
msg =  "success"  if  user = =  "jatsz"  else  "fail"

怎麼辦?

一個變通的辦法是「提取函數」,咱們將這行語句提取成函數,而後對提取出來的函數應用裝飾器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def  printdebug(func):
     def  __decorator(user):   
         print ( 'enter the login' )
         result =  func(user)
         print ( 'exit the login' )
         return  result     
     return  __decorator 
 
def  login(user):
     print ( 'in login:'  +  user)
     msg =  validate(user)  #exact to a method
     return  msg 
 
@printdebug   #apply the decorator for exacted method
def  validate(user):
     msg =  "success"  if  user = =  "jatsz"  else  "fail"
     return  msg
 
result1 =  login( 'jatsz' );
print  result1

 

來個更加真實的應用,有時候validate是個耗時的過程。爲了提升應用的性能,咱們會將validate的結果cache一段時間(30 seconds),藉助decorator和上面的方法,咱們能夠這樣實現:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import  time
 
dictcache =  {}
 
def  cache(func):
     def  __decorator(user):   
         now =  time.time()
         if  (user in  dictcache):
             result,cache_time =  dictcache[user]
             if  (now -  cache_time) > 30 #cache expired
                 result =  func(user)
                 dictcache[user] =  (result, now)  #cache the result by user
             else :
                 print ( 'cache hits' )
         else :
             result =  func(user)
             dictcache[user] =  (result, now)
         return  result     
     return  __decorator 
 
def  login(user):
     print ( 'in login:'  +  user)
     msg =  validate(user) 
     return  msg 
 
@cache   #apply the cache for this slow validation
def  validate(user):
     time.sleep( 5 #simulate 10 second block
     msg =  "success"  if  user = =  "jatsz"  else  "fail"
     return  msg
 
result1 =  login( 'jatsz' ); print  result1 
result2 =  login( 'jatsz' ); print  result2    #this login will return immediately by hit the cache
result3 =  login( 'candy' ); print  result3

 


Reference:

http://stackoverflow.com/questions/739654/understanding-python-decorators      --Understanding Python decorators

http://www.cnblogs.com/rhcad/archive/2011/12/21/2295507.html   --Python裝飾器學習(九步入門)

http://www.python.org/dev/peps/pep-0318/   --PEP 318 -- Decorators for Functions and Methods

相關文章
相關標籤/搜索