【Python】淺談裝飾器

楊奇龍發表於2017-05-01
一 裝飾器是什麼
   裝飾器是一個用於封裝函式或者類的程式碼工具,顯式地將封裝器作用於函式或者類上,達到程式執行時動態增加功能的目的。對於函式執行前處理常見前置條件(常見的web登陸授權驗證),或者在函式執行之後做善後工作(比如異常處理,記錄log 等等)。

二 如何使用裝飾器
   裝飾器本質上就是一個可用接受呼叫也可以返回撥用的高階函式。該函式以被裝飾的函式為引數(還可以加上其他值作為引數)。在裝飾器內進行裝飾器的邏輯處理,執行被裝飾函式,並返回一個裝飾過的函式,聽起來是不是有點繞,Talk is cheap,show me the code . 
本文使用函式now 和函式add作為例子,
  1. import datetime
  2. def now():
  3.     print 'now is ', datetime.datetime.today()
  4. def add(x, y):
  5.     ret = x + y
  6.     print '{x} + {y} = {retval}'.format(x=x,y=y,retval=ret)
2.1 裝飾器語法
   有兩種方式顯示呼叫裝飾器的方法。
   方法一:func = deco(func)
   方法二:Python 2.5之後 為裝飾器引入了特殊的語法 @ --語法糖,在裝飾器名稱前使用@ 符號,新增在被裝飾的函式定義之前。 
  1. @deco
  2.    def now():
  3.        print 'now is ', datetime.datetime.today()
  4.    # 呼叫now
  5.    now()
本文將從 引數這個角度來由淺入深介紹裝飾器,函式有不帶引數和帶引數的兩種情況,裝飾器也有帶引數和不帶引數的兩種情況,裝飾器對處理帶引數和不帶引數的情況也會有鎖不同。
2.2 不帶引數的情況
  我們需要在呼叫函式 now 之前和之後加上呼叫記錄。
  1. def deco(func):
  2.     print 'begin call %s():' % (func.__name__)
  3.     func()
  4.     print 'end call %s():' % (func.__name__)
  5.     return func #裝飾器的引數是被裝飾的函式物件,返回原函式的物件。
  1. yangyiDBA:test yangyi$ python 1.py
  2. begin call now():
  3. now is 2017-05-01 14:40:57.309836
  4. end call now():
  5. now is 2017-05-01 14:40:57.309868
但是從上面的例子看 結果輸出了兩次now 時間,明顯不符合我們的要求,因為裝飾器必須返回被呼叫函式,return func的時候發生了第二次。後面我們會解決這個問題。
2.3 帶引數的情況,因為函式的引數個數是不確定的 ,我們需要藉助(*args, **kwargs),自動適應變參和命名引數。
  1. #!/usr/bin/env python
  2. # coding:utf-8
  3. import datetime
  4. import functools
  5. def deco(func):
  6.     @functools.wraps(func) #
  7.     def wrapper(*args, **kw):
  8.         print 'begin call %s():' % (func.__name__)
  9.         result=func(*args, **kw) # 如果函式無返回值 ,可以直接使用func(*args, **kw)
  10.         print 'end call %s():' % (func.__name__)
  11.         return result #這裡 result 是為了func 有返回值,
  12.     return wrapper
  13. @deco
  14. def add(x, y):
  15.     ret = x + y
  16.     print '{x} + {y} = {retval}'.format(x=x,y=y,retval=ret)
  17. @deco
  18. def now():
  19.     print 'now is ', datetime.datetime.today()
  20. add(2,5)
  21. now()
上面的裝飾器做了如下事情
1 函式func作為引數傳給 deco()。
2 functool.wraps 將func 的屬性複製給 warper。
3 執行函式func前後執行某些動作。
4 返回結果。
5 返回wrapper 函式物件。
這裡特別說明functool.wraps的作用,由於裝飾器導致直譯器認為函式本身發生了改變,在某些情況下可能會導致一些問題。Python透過functool.wraps解決了這個問題:
在編寫裝飾器時,在實現前加入 @functools.wraps(func) 可以保證裝飾器不會對被裝飾函式造成影響。
特別說明其他要使用裝飾器的時候會有其他的寫法 比如直接返回被裝飾的函式。
  1. def deco(func):
  2.     @functools.wraps(func) #
  3.     def wrapper(*args, **kw):
  4.         print 'begin call %s():' % (func.__name__)
  5.         return func(*args, **kw)
  6.     return wrapper
輸出
  1. yangyiDBA:test yangyi$ python 1.py
  2. begin call add():
  3. 2 + 5 = 7
  4. end call add():
  5. begin call now():
  6. now is 2017-05-01 15:20:51.597859
  7. end call now():
2.4 帶引數的裝飾器 
如果裝飾器本身傳入引數,就需要編寫一個返回decorator的高階函式,寫出來會更復雜。比如,要自定義log的文字:
  1. #!/usr/bin/env python
  2. # coding:utf-8
  3. import datetime
  4. import functools
  5. def deco(text):
  6.     def _deco(func):
  7.         def wrapper(*args, **kw):
  8.             print '%s, begin call %s():' % (text,func.__name__)
  9.             result=func(*args, **kw) # 如果函式無返回值 ,可以直接使用func(*args, **kw)
  10.             print '%s, end call %s():' % (text,func.__name__)
  11.             return result #這裡 result 是為了func 有返回值,
  12.         return wrapper
  13.     return _deco
  14. @deco("yangyi")
  15. def add(x, y):
  16.     ret = x + y
  17.     print '{x} + {y} = {retval}'.format(x=x,y=y,retval=ret)
  18. @deco("youzan")
  19. def now():
  20.     print 'now is ', datetime.datetime.today()
  21. add(2,5)
  22. now()
測試結果:
  1. yangyiDBA:test yangyi$ python 2.py
  2. yangyi, begin call add():
  3. 2 + 5 = 7
  4. yangyi, end call add():

  5. youzan, begin call now():
  6. now is 2017-05-01 18:47:54.728296
  7. youzan, end call now():
2.5 Python內建裝飾器
在Python中有三個內建的裝飾器,都是跟class相關的:staticmethod、classmethod 和property。
staticmethod 是類靜態方法,其跟成員方法的區別是沒有 self 引數,並且可以在類不進行例項化的情況下呼叫
classmethod  與成員方法的區別在於所接收的第一個引數不是 self (類例項的指標),而是cls(當前類的具體型別)
property 是屬性的意思,表示可以透過透過類例項直接訪問的資訊

2.6 跨檔案呼叫,因為裝飾器本質是一個函式。在工程實現裡我們可以透過建立一個公用的decorator,作為基礎裝飾器供其他函式呼叫。
 
三 小結
   Python一切皆物件,函式也是,也可以賦值給其他變數,理解這點再去理解裝飾器就容易多了。剛剛研究裝飾器,總結的可能比較淺顯,想要深入學習裝飾器的可以看看下面的 參考文章。

四 參考文章
1 《Python高階程式設計》 第一章裝飾器
2 《》
3  Python裝飾器學習(九步入門) 
4  《詳解Python的裝飾器》

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/22664653/viewspace-2138264/,如需轉載,請註明出處,否則將追究法律責任。

相關文章