????????在定義函數之后期贫,希望擴展這些函數的功能梳星,譬如在函數調用前后自動打印日志赞赖,但如果是一些通用的功能,修改每一個函數又會顯得比較麻煩冤灾。最好的方法就是定義一個裝飾器前域,給每個函數增加功能。這種在代碼運行期間動態(tài)增加函數功能的方式韵吨,稱為裝飾器(Decorator)
原理
不帶參數的裝飾器
@a_decorator
def f(...):?
?????????...
#經過a_decorator后匿垄, 函數f就相當于以f為參數調用a_decorator返回結果。
f = a_decorator(f)
來分析這個式子归粉, 可以看出至少要滿足以下幾個條件?
1. 裝飾器函數運行在函數定義的時候?
2. 裝飾器需要返回一個可執(zhí)行的對象?
3. 裝飾器返回的可執(zhí)行對象要兼容函數f的參數
初始函數
from datetime import datetime
def now():?
? ? ????print(datetime.now())?
>>>now()
增加功能
def now():?
????????print('run %s().' % now.__name__)
? ? ? ? print(datetime.now())
? ? ? ? print('run %s() finished.' % now.__name__)
>>>now()
def decorator(fun): ? ? ? ?#將函數作為參數傳入
????????def wrapper(*args,**kw):?
????????????????print('run %s().' % fun.__name__)
? ? ? ? ? ? ????outcome = fun(*args,**kw)
? ? ? ? ? ? ????print('run %s() finished.' % fun.__name__)
? ? ? ? ? ? ????return outcome ? ?#將函數作為返回值返回
? ? ? ? return wrapper
now = decorator(now)
now()
????????這是裝飾器最直觀的表示了椿疗,定義一個裝飾器函數,然后將自己的函數傳入糠悼,輸出的新函數即添加了新功能届榄。本質上來看,裝飾器就是一個高階函數倔喂。由于其接收參數為(*args,**kw),所以能夠接受任何形式的調用铝条。在wrapper函數內,調用傳入的fun()函數之外席噩,就可以定義新的功能班缰。
__name__問題
>>>now()
run now().
2016-05-31 17:10:32.217998
run now() finished.
>>>now.__name__
'wrapper' ? ? ? #在這里名字應該為now,被改變了
????????調用完成之后班挖,新的問題又出現(xiàn)了:函數的__name__屬性因為使用裝飾器改變了鲁捏,有些依賴函數簽名的代碼執(zhí)行就會出錯。
python內置的functools.wraps能夠將函數名稱替換回來萧芙。一個完整的decorator寫法如下:
>>> import functools
>>> def decorator(fun):?
?????????????????@functools.wraps(fun)?
?????????????????def wrapper(*args,**kw):?
?????????????????????????print('run %s().' % fun.__name__)
? ? ? ? ? ? ? ? ? ? ? ? ?outcome = fun(*args,**kw)
? ? ? ? ? ? ? ? ? ? ? ? ?print('run %s() finished.' % fun.__name__)
? ? ? ? ? ? ????????????return outcome
? ? ? ????????? return wrapper
>>> @decorator?
?????????def now():?
?????????????????print(datetime.now())
>>> now()
run now().2016-05-31 17:32:59.270645
run now() finished.
>>> now.__name__
'now'
那么给梅,問題來了,我們能不能創(chuàng)建一個裝飾器双揪,能夠傳入參數动羽,也可以不傳入參數呢?
這需要對裝飾器的運行過程有充分的了解渔期,觀察上面的兩類裝飾器运吓,主要的區(qū)別在于:
沒有參數的裝飾器渴邦,最外層接收的是func函數,最終返回的函數是wrapper(*args,**kw)拘哨,直接接收func函數參數
帶參數的裝飾器谋梭,最外層接收的是參數,而最終返回的函數是wrapper(func)倦青,接收func函數
那么瓮床,我們就可以通過判定最外層的函數接收的是func函數還是參數,來決定是有參數或者是無參數的裝飾器产镐,最終決定如何返回隘庄。嘗試結果如下:
裝飾器類
????????在Python中, 其實函數也是對象癣亚。 反過來丑掺, 對象其實也可以像函數一樣調用, 只要在類的方法中實現(xiàn)__call__()方法。
????????__new__: 對象的創(chuàng)建述雾,是一個靜態(tài)方法街州,第一個參數是cls。(想想也是绰咽,不可能是self菇肃,對象還沒創(chuàng)建地粪,哪來的self)
(__new__方法在類定義中不是必須寫的取募,如果沒定義,默認會調用object.__new__去創(chuàng)建一個對象蟆技。如果定義了玩敏,就是override,可以custom創(chuàng)建對象的行為。聰明的讀者可能想到质礼,既然__new__可以custom對象的創(chuàng)建旺聚,那我在這里做一下手腳,每次創(chuàng)建對象都返回同一個眶蕉,那不就是單例模式了嗎)
__init__ : 對象的初始化砰粹, 是一個實例方法,第一個參數是self造挽。
(__init__其實不是實例化一個類的時候第一個被調用的方法碱璃。當使用 Persion(name, age) 這樣的表達式來實例化一個類時,最先被調用的方法 其實是 __new__ 方法饭入。)
__call__ : 對象可call嵌器,注意不是類,是對象
(對象通過提供__call__(slef, [,*args [,**kwargs]])方法可以模擬函數的行為谐丢,如果一個對象x提供了該方法爽航,就可以像函數一樣使用它蚓让,也就是說x(arg1, arg2...) 等同于調用x.__call__(self, arg1, arg2) 。模擬函數的對象可以用于創(chuàng)建防函數(functor) 或代理(proxy).)