誰能夠做爲裝飾器(能夠將誰編寫成裝飾器):html
__call__
的可調用類裝飾器能夠去裝飾誰(誰能夠被裝飾):python
假如你已經定義了一個函數funcA(),在準備定義函數funcB()的時候,若是寫成下面的格式:app
@funcA def funcB():...
表示用函數funcA()裝飾函數funcB()。固然,也能夠認爲是funcA包裝函數funcB。它等價於:函數
def funcB():... funcB = funcA(funcB)
也就是說,將函數funcB做爲函數funcA的參數,funcA會從新返回另外一個可調用的對象(好比函數)並賦值給funcB。編碼
因此,funcA要想做爲函數裝飾器,須要接收函數做爲參數,而且返回另外一個可調用對象(如函數)。例如:code
def funcA(F): ... ... return Callable
注意,函數裝飾器返回的可調用對象並不必定是原始的函數F,能夠是任意其它可調用對象,好比另外一個函數。但最終,這個返回的可調用對象都會被賦值給被裝飾的函數變量(上例中的funcB)。htm
函數能夠同時被多個裝飾器裝飾,後面的裝飾器之前面的裝飾器處理結果爲基礎進行處理:對象
@decorator1 @decorator2 def func():... # 等價於 func = decorator1(decorator2(func))
當調用被裝飾後的funcB時,將自動將funcB進行裝飾,並調用裝飾後的對象。因此,下面是等價的調用方式:blog
funcB() # 調用裝飾後的funcB funcA(funcB)()
瞭解完函數裝飾器的表現後,大概也能猜到了,裝飾器函數能夠用來擴展、加強另一個函數。實際上,內置函數中staticmethod()、classmethod()和property()都是裝飾器函數,能夠用來裝飾其它函數,在後面會學到它們的用法。字符串
例如,函數f()返回一些字符串,如今要將它的返回結果轉換爲大寫字母。能夠定義一個函數裝飾器來加強函數f()。
def toupper(func): def wrapper(*args, **kwargs): result = func(*args, **kwargs) return result.upper() return wrapper @toupper def f(x: str): # 等價於f = toupper(f) return x res = f("abcd") print(res)
上面toupper()裝飾f()後,調用f("abcd")
的時候,等價於執行toupper(f)("abcd")
,參數"abcd"傳遞給裝飾器中的wrapper()中的*args
,在wrapper中又執行了f("abcd")
,使得本來屬於f()的整個過程都完整了,最後返回result.upper()
,這部分是對函數f()的擴展部分。
注意,上面的封裝函數wrapper()中使用了*args **kwargs
,是爲了確保任意參數的函數都能正確執行下去。
再好比要計算一個函數autodown()的執行時長,能夠額外定義一個函數裝飾器timecount()。
import time # 函數裝飾器 def timecount(func): def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(func.__name__, end - start) return result return wrapper # 裝飾函數 @timecount def autodown(n: int): while n > 0: n -= 1 # 調用被裝飾的函數 autodown(100000) autodown(1000000) autodown(10000000)
執行結果:
autodown 0.004986763000488281 autodown 0.05684685707092285 autodown 0.5336081981658936
上面wrapper()中的return是多餘的,是由於這裏裝飾的autodown()函數自身沒有返回值。但卻不該該省略這個return,由於timecount()能夠去裝飾其它可能有返回值的函數。
前面的裝飾器代碼邏輯上沒有什麼問題,可是卻存在隱藏的問題:函數的元數據信息丟了。好比doc、註解等。
好比下面的代碼:
@timecount def autodown(n: int): ''' some docs ''' while n > 0: n -= 1 print(autodown.__name__) print(autodown.__doc__) print(autodown.__annotations__)
執行結果爲:
wrapper None {}
因此,必需要將被裝飾函數的元數據保留下來。可使用functools模塊中的wraps()裝飾一下裝飾器中的wrapper()函數。以下:
import time from functools import wraps def timecount(func): @wraps(func) def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(func.__name__, end - start) return result return wrapper
如今,再去查看autodown函數的元數據信息,將會獲得被保留下來的內容:
autodown some doc {'n': <class 'int'>}
因此,wraps()的簡單用法是:向wraps()中傳遞的func參數,那麼func的元數據就會被保留下來。
上面@wraps(func)
裝飾wrapper的過程等價於:
def wrapper(*args, **kwargs):... wrapper = wraps(func)(wrapper)
請注意這一點,由於在將類做爲裝飾器的時候,常常會在__init__(self, func)
裏這樣使用:
class cls: def __init__(self, func): wraps(func)(self) ... def __call__(self, *args, **kwargs): ...
函數被裝飾後,如何再去訪問未被裝飾狀態下的這個函數?@wraps還有一個重要的特性,能夠經過被裝飾對象的__wrapped__
屬性來直接訪問被裝飾對象。例如:
autodown.__wrapped__(1000000) new_autodown = autodown.__wrapped__ new_autodown(1000000)
上面的調用不會去調用裝飾後的函數,因此不會輸出執行時長。
注意,若是函數被多個裝飾器裝飾,那麼經過__wrapped__
,將只會解除第一個裝飾過程。例如:
@decorator1 @decorator2 @decorator3 def f():...
當訪問f.__wrapped__()
的時候,只有decorator1被解除,剩餘的全部裝飾器仍然有效。注意,python 3.3以前是略過全部裝飾器。
下面是一個多裝飾的示例:
from functools import wraps def decorator1(func): @wraps(func) def wrapper(*args, **kwargs): print("in decorator1") return func(*args, **kwargs) return wrapper def decorator2(func): @wraps(func) def wrapper(*args, **kwargs): print("in decorator2") return func(*args, **kwargs) return wrapper def decorator3(func): @wraps(func) def wrapper(*args, **kwargs): print("in decorator3") return func(*args, **kwargs) return wrapper @decorator1 @decorator2 @decorator3 def addNum(x, y): return x+y
返回結果:
in decorator1 in decorator2 in decorator3 5 in decorator2 in decorator3 5
若是不使用functools的@wraps的__wrapped__
,想要手動去引用原始函數,須要作的工做可能會很是多。因此,若有須要,直接使用__wrapped__
去調用未被裝飾的函數比較好。
另外,並非全部裝飾器中都使用了@wraps
。
函數裝飾器也是能夠帶上參數的。
@decorator(x,y,z) def func():...
它等價於:
func = decorator(x,y,z)(func)
它並非"天生"就這樣等價的,而是根據編碼規範編寫裝飾器的時候,一般會這樣。其實帶參數的函數裝飾器寫起來有點繞:先定義一個帶有參數的外層函數,它是外在的函數裝飾器,這個函數內包含了真正的裝飾器函數,而這個內部的函數裝飾器的內部又包含了被裝飾的函數封裝。也就是函數嵌套了一次又一次。
因此,結構大概是這樣的:
def out_decorator(some_args): ...SOME CODE... def real_decorator(func): ...SOME CODE... def wrapper(*args, **kwargs): ...SOME CODE WITH func... return wrapper return real_decorator # 等價於func = out_decorator(some_args)(func) @out_decorator(some_args) def func():...
下面是一個簡單的例子:
from functools import wraps def out_decorator(x, y, z): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): print(x) print(y) print(z) return func(*args, **kwargs) return wrapper return decorator @out_decorator("xx", "yy", "zz") def addNum(x, y): return x+y print(addNum(2, 3))
根據前面介紹的兩種狀況,裝飾器能夠帶參數、不帶參數,因此有兩種裝飾的方式,要麼是下面的(1),要麼是下面的(2)。
@decorator # (1) @decorator(x,y,z) # (2)
因此,根據不一樣的裝飾方式,須要編寫是否帶參數的不一樣裝飾器。
可是如今想要編寫一個將上面兩種參數方式統一塊兒來的裝飾器。
可能第一想法是讓裝飾器參數默認化:
def out_decorator(arg1=X, arg2=Y...): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): ... return wrapper return decorator
如今能夠用下面兩種方式來裝飾:
@out_decorator() @out_decorator(arg1,arg2)
雖然上面兩種裝飾方式會正確進行,但這並不是合理作法,由於下面這種最通用的裝飾方式會錯誤:
@out_decorator
爲了解決這個問題,回顧下前面裝飾器是如何等價的:
# 等價於 func = decorator(func) @decorator def func():... # 等價於 func = out_decorator(x, y, z)(func) @out_decorator(x, y, z) def func():...
上面第二種方式中,out_decorator(x,y,z)纔是真正返回的內部裝飾器。因此,能夠修改下裝飾器的編寫方式,將func也做爲out_decorator()的其中一個參數:
from functools import wraps,partial def decorator(func=None, arg1=X, arg2=Y): # 若是func爲None,說明觸發的帶參裝飾器 # 直接返回partial()封裝後的裝飾器函數 if func is None: decorator_new = partial(decorator, arg1=arg1, arg2=arg2) return decorator_new #return partial(decorator, arg1=arg1, arg2=arg2) # 下面是裝飾器的完整裝飾內容 @wraps(func) def wrapper(*args, **kwargs): ... return wrapper
上面使用了functools模塊中的partial()函數,它能夠返回一個新的將某些參數"凍結"後的函數,使得新的函數無需指定這些已被"凍結"的參數,從而減小參數的數量。若是不知道這個函數,參考partial()用法說明。
如今,能夠統一下面3種裝飾方式:
@decorator() @decorator(arg1=x,arg2=y) @decorator
前兩種裝飾方式,等價的調用方式是decorator()(func)
和decorator(arg1=x,arg2=y)(func)
,它們的func都爲None,因此都會經過partial()返回一般的裝飾方式@decorator
所等價的形式。
須要注意的是,由於上面的參數結構中包含了func=None
做爲第一個參數,因此帶參數裝飾時,必須使用keyword格式來傳遞參數,不能使用位置參數。
下面是一個簡單的示例:
from functools import wraps, partial def decorator(func=None, x=1, y=2, z=3): if func is None: return partial(decorator, x=x, y=y, z=z) @wraps(func) def wrapper(*args, **kwargs): print("x: ", x) print("y: ", y) print("z: ", z) return func(*args, **kwargs) return wrapper
下面3種裝飾方式均可以:
@decorator def addNum(a, b): return a + b print(addNum(2, 3)) print("=" * 40) @decorator() def addNum(a, b): return a + b print(addNum(2, 3)) print("=" * 40) # 必須使用關鍵字參數進行裝飾 @decorator(x="xx", y="yy", z="zz") def addNum(a, b): return a + b print(addNum(2, 3))
返回結果:
x: 1 y: 2 z: 3 5 ==================== x: 1 y: 2 z: 3 5 ==================== x: xx y: yy z: zz 5