一般咱們見到的簡單裝飾器這樣的:編程
import json import functools def json_output(func): @functools.wraps(decorated) def inner(*args, **kwargs): result = func(*args, **kwargs) return json.dumps(result) return inner @json_output def f(): return {'status': 'done'}
當裝飾器應用於函數 f
上時,它接受 f
做爲其參數,返回一個函數 inner
,且將他綁定到變量f上。json
示例中咱們編寫的裝飾器 json_output
只接受一個隱式參數——即被裝飾的方法,在使用此裝飾器時自己看上去是並無參數的。然而有時候須要讓裝飾器自身帶有一些須要的信息,從而使裝飾器可使用恰當的方式裝飾方法。好比上面的例子中,咱們想經過向裝飾器傳入不一樣的參數來控制輸出結果的縮進(indent)和排序(sort)。咱們能夠這麼作:函數
import json import functools def json_output(indent=None, sort_keys=False): def actual_decorator(func): @functools.wraps(func) def inner(*args, **kwargs): result = func(*args, **kwargs) return json.dumps(result, indent=indent, sort_keys=sort_keys) return inner return actual_decorator @json_output(indent=4) def f(): return {'status': 'done'}
初次看起來會以爲比較繞人,由於函數裏嵌套了兩個函數定義,然而實際上和以前一個版本的區別在於爲了接收json序列化的參數多包裝了一層,因此code
@json_output(indent=4) def f(): return {'status': 'done'} # 至關於 @actual_decorator def f(): return {'status': 'done'}
這樣看起來就會明晰不少。排序
實際上, 裝飾器裏的 @
後接收一個函數,該函數以被裝飾的函數(例子中是f)爲參數,而且返回一個函數。當須要在裝飾函數的同時傳入參數的話,那麼就須要多包裝一層,先傳入參數(例子中是 indent=4
)返回一個裝飾的函數(例子中是 actual_decorator
), 這個返回的的函數 就跟之前同樣接受被裝飾的函數(f)做爲參數而且返回一個函數做爲裝飾最後的方法供調用。import
然而當咱們像上面那樣定義裝飾器時,就不能這樣調用:變量
import json import functools def json_output(indent=None, sort_keys=False): def actual_decorator(func): @functools.wraps(func) def inner(*args, **kwargs): result = func(*args, **kwargs) return json.dumps(result, indent=indent, sort_keys=sort_keys) return inner return actual_decorator @json_output def f(): return {'status': 'done'}
在實際的項目過程當中,有時會出現這樣的情況: 一開始寫的裝飾器時不須要使用時傳參數的,後來發現有必要傳參數,改好後原來不傳參的裝飾器不能正常使用了,這是修改原來使用的地方是項痛苦的事情。
這時候就須要對裝飾器作一個兼容,使它在如下狀況均可用:序列化
@json_output @json_output() @json_output(indent=4)
具體作法以下:方法
import json import functools def json_output(decorated_=None, indent=None, sort_keys=False): if decorated_ and (indent or sort_keys): raise def actual_decorator(func): @functools.wraps(func) def inner(*args, **kwargs): result = func(*args, **kwargs) return json.dumps(result, indent=indent, sort_keys=sort_keys) return inner if decorated_: return actual_decorator(decorated_) else: return actual_decorator @json_output(indent=4) def f1(): return {'status': 'done'} @json_output def f2(): return {'status': 'done'} @json_output() def f3(): return {'status': 'done'} print f1() print f2() print f3()
代碼中關鍵的地方在於 json_output
在最後對參數 decorated
進行了判斷,有的話證實是不傳參調用,那麼直接返回 actual_decorator
的調用;沒有的話則表明是傳參類型的調用(雖然參數可能不存在),那麼返回 actual_decorator
。其中有點須要注意, josn_output
的傳參須要使用關鍵字參數,若是像下面這樣直接傳一個位置參數,那麼根據如今的實現會出現錯誤(由於它會被當成 decorated_
)。im
@json_output(4) #錯誤的使用方法 def f4(): return {'status': 'done'}
《Python高級編程》 by Luke Sneeriger