Python中的裝飾器(decorator)

來源:互聯網
上載者:User
文章目錄
  • 1,起源
  • 2,讓代碼變得優美一點
  • 3,讓代碼再優美一點
  • 4,加上參數
  • 5,裝飾有傳回值的函數
  • 6,應用多個裝飾器
  • 7,靈活運用

想理解Python的decorator首先要知道在Python中函數也是一個對象,所以你可以

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

函數在Python中給變數的用法一樣也是一等公民,也就是高階函數(High Order Function)。所有的魔法都是由此而來。

1,起源

我們想在函數login中輸出調試資訊,我們可以這樣做

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 valuedebug_login = printdebug(login)  #function assign to variabledebug_login()  #execute the returned function

這樣我們每次只要調用debug_login就可以了,這個名字更符合直覺。我們將原先的兩個函數printdebug和login綁定到一起,成為debug_login。這種耦合叫內聚:-)。

3,讓代碼再優美一點

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

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

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

可以看出decorator就是一個:使用函數作參數並且返回函數的函數。通過改進我們可以得到:

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

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

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 informationprint('enter the login')func(user)  #pass user to loginprint('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 parameterdef printdebug(func):def __decorator(user):    print('enter the login, and debug level is: ' + str(level)) #print debug levelfunc(user)  print('exit the login')return __decorator  return printdebug    #return original decorator@printdebug_level(level=5)   #decorator's parameter, debug level set to 5def 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是否成功。

login_result = login(‘jatsz’)

我們需要將傳回值在decorator和調用函數間傳遞:

def printdebug(func):def __decorator(user):    print('enter the login')result = func(user)  #recevie the native function call resultprint('exit the login')return result        #return to callerreturn __decorator  @printdebug  def login(user):print('in login:' + user)msg = "success" if user == "jatsz" else "fail"return msg  #login with a return valueresult1 = login('jatsz');print result1  #print login resultresult2 = login('candy');print result2

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

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

6,應用多個裝飾器

我們可以對一個函數應用多個裝飾器,這時我們需要留心的是應用裝飾器的順序對結果會產生。影響比如:

def printdebug(func):def __decorator():    print('enter the login')func() print('exit the login')return __decorator  def others(func):    #define a other decoratordef __decorator():print '***other decorator***'func()return __decorator@others         #apply two of decorator@printdebugdef login():print('in login:')@printdebug    #switch decorator order@othersdef logout():print('in logout:')login()print('---------------------------') logout()

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

$ python deoc.py***other decorator***enter the loginin 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函數應用裝飾器的過程這樣看(虛擬碼):

@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函數是一個整體,當我們想對部分函數應用裝飾器時,裝飾器變的無從下手。比如我們想對下面這行語句應用裝飾器:

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

怎麼辦?

一個變通的辦法是“提取函數”,我們將這行語句提取成函數,然後對提取出來的函數應用裝飾器:

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 methodreturn msg  @printdebug  #apply the decorator for exacted methoddef validate(user):msg = "success" if user == "jatsz" else "fail" return msgresult1 = login('jatsz');print result1  

 

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

import timedictcache = {}def cache(func):def __decorator(user):    now = time.time()if (user in dictcache):result,cache_time = dictcache[user]if (now - cache_time) > 30:  #cache expiredresult = func(user)dictcache[user] = (result, now)  #cache the result by userelse: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 validationdef validate(user):time.sleep(5)  #simulate 10 second blockmsg = "success" if user == "jatsz" else "fail" return msgresult1 = login('jatsz'); print result1  result2 = login('jatsz'); print result2    #this login will return immediately by hit the cacheresult3 = 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

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.