函數裝飾器裝飾普通函數已經很容易理解了:html
@decorator def func():... #等價於 def func():... func = decorator(func)
若是裝飾器是帶參裝飾器,那麼等價的形式大概是這樣的(和裝飾器的編碼有關,但最廣泛的編碼形式以下):app
@decorator(x, y, z) def func():... # 等價於 def func():... func = decorator(x, y, z)(func)
這樣的函數裝飾器也能夠去裝飾類中的方法。看下面的方法裝飾形式:函數
class cls: @decorator def method(self,arg1,arg2): ...
它等價於:ui
class cls: def method(self,arg1,arg2): ... method = decorator(method)
在decorator的編碼中,仍然像普通的函數裝飾器同樣編寫便可。例如:編碼
def decorator(F): @wraps(F) def wrapper(*args, **kwargs): ... # args[0] = self_instance # args[1]開始纔是手動傳給method的參數 return wrapper
但必需要考慮到method的第一個參數self,因此包裝器wrapper()的第一個參數也是self。code
如此一來,函數裝飾器既能夠裝飾函數,又能夠裝飾方法。htm
下面是一個示例:對象
from functools import wraps def decorator(F): @wraps(F) def wrapper(*args, **kwargs): result = F(*args, **kwargs) print(args) return result return wrapper @decorator def func(x,y): return x + y print(func(3, 4)) print("-" * 30) class cls: @decorator def method(self, x, y): return x + y c = cls() print(c.method(3, 4))
輸出結果:blog
(3, 4) 7 ------------------------------ (<__main__.cls object at 0x01DF1C50>, 3, 4) 7
不只函數能夠做爲裝飾器,類也能夠做爲裝飾器去裝飾其它對象。get
要讓類做爲裝飾器,先看裝飾的形式:
class Decorator: ... @Decorator def func(): ... func(arg1, arg2)
若是成功裝飾,那麼它等價於:
def func(): ... func = Decorator(func) func(arg1, arg2)
這和函數裝飾器看上去是同樣的,但區別在於Decorator這裏是一個類,而不是函數,且Decorator(func)
表示的是建立一個Decorator類的實例對象,因此這裏賦值符號左邊的func是一個對象。全部後面的func(arg1, arg2)
是調用對象,而不是調用函數。
要讓實例對象成爲可調用對象,它必須實現__call__
方法,因此應該在Decorator類中定義一個__call__
。並且每次調用實例對象的時候,都是在調用__call__
,這裏的__call__
對等於函數裝飾器中的包裝器wrapper
,因此它的參數和邏輯應當和wrapper同樣。
以下:
class Decorator(): def __call__(self, *args, **kwargs): ...
再看func = Decorator(func)
,func是Decorator類建立實例的參數,因此Decorator類還必須實現一個__init__
方法,接受func做爲參數:
class Decorator: def __init__(self, func): ... def __call__(self, *args, **kwargs): ...
這樣的裝飾器已經能正常工做了,可是會丟失func的元數據信息。因此,必須使用functools的wraps()保留func的元數據:
from functools import wraps class Decorator: def __init__(self, func): wraps(func)(self) ... def __call__(self, *args, **kwargs): ...
爲何是wraps(func)(self)
?這裏顯然不能@wraps(func)
的方式裝飾包裝器,因此只能使用wraps()的原始函數形式。在wraps()裝飾函數包裝器wrapper的時候,@wraps(func)
等價於wrapper = wraps(func)(wrapper)
,因此這裏wraps(func)(self)
的做用也是很明顯的:保留func的元數據,並裝飾self。被裝飾的self是什麼?是Decorator的實例對象,由於Decorator類實現了__call__
,因此self是可調用的,因此這裏的self相似於函數裝飾器返回的wrapper函數(實際上self是Decorator(func)返回的各個實例對象)。
雖然self是Decorator的可調用實例對象,可是上面的代碼中self並不具備func屬性,也就是說沒法從self去調用func()函數,這彷佛使得整個過程都崩塌了:廢了老大的勁去解決各類裝飾器上的問題,結果卻不能調用被裝飾的函數。
有兩種方式能夠解決這個問題:
__init__
中使用self.func = func
保留func對象做爲裝飾器的一個屬性__call__
中使用__wrapped__
調用原始func函數這兩種方式實際上是等價的,由於self.func
和__wrapped__
都指向原始的函數。
def __init__(self,func): wraps(func)(self) self.func = func def __call__(self, *args, **kwargs): result = self.func(*args, **kwargs) #------------------------------- def __init__(self, func): wraps(func)(self) def __call__(self, *args, **kwargs): result = self.__wrapped__(*args, **kwargs)
但這兩種方式都有缺陷,缺陷在於裝飾類中方法時。(注:在裝飾普通函數、類方法的時候,上面的方式不會出錯)
class cls: @decorator def method(self, x, y):...
由於self.func
和__wrapped__
裝飾cls中的方法時指向的都是cls的類變量(只不過這個屬性是裝飾器類decorator的實例對象而已),做爲類變量,它沒法保存cls的實例對象,也就是說method(self, x, y)
的self對裝飾器是不可見的。
用一個示例解釋更容易:
import types from functools import wraps # 做爲裝飾器的類 class decorator: def __init__(self,func): self.func = func def __call__(self, *args, **kwargs): print("(1): ",self) # (1) print("(2): ",self.func) # (2) print("(3): ",args) # (3) return self.func(*args, **kwargs) class cls: @decorator def method(self, x, y): return x + y c = cls() print("(4): ",c.method) # (4) print(c.method(3, 4))
輸出結果:
(4): <__main__.decorator object at 0x03261630> (1): <__main__.decorator object at 0x03261630> (2): <function cls.method at 0x032C2738> (3): (3, 4) Traceback (most recent call last): File "g:/pycode/scope.py", line 21, in <module> print(c.method(3, 4)) File "g:/pycode/scope.py", line 12, in __call__ return self.func(*args, **kwargs) TypeError: method() missing 1 required positionalargument: 'y'
注意觀察上面__call__
中輸出的幾個對象:
若是將上面的method()的定義修改一下,把self去掉,將會正確執行:
class cls: @decorator def method(x, y): return x + y
執行結果:
(4): <__main__.decorator object at 0x03151630> (1): <__main__.decorator object at 0x03151630> (2): <function cls.method at 0x031B2738> (3): (3, 4) 7
所以參數問題必須解決。解決方案是進行判斷:若是是經過實例對象觸發的方法調用(即c.method()),就將外部函數經過types.MethodType()
連接到這個實例對象中,不然就返回原始self(由於它指向被裝飾的原始對象)。
這須要藉助描述符來實現,關於這一段的解釋,我以爲直接看代碼自行腦部更佳。
class decorator: def __init__(self,func): wraps(func)(self) self.func = func def __call__(self, *args, **kwargs): return self.func(*args, **kwargs) def __get__(self, instance, owner): # 若是不是經過對象來調用的 if instance is None: return self else: return types.MethodType(self, instance) class cls: @decorator def method(self, x, y): return x + y c = cls() print(c.method(3, 4)) # 調用__get__後調用__call__
對於__wrapped__
也同樣可行:
class decorator(): def __init__(self, func): wraps(func)(self) def __call__(self, *args, **kwargs): return self.__wrapped__(*args, **kwargs) def __get__(self, instance, owner): if instance is None: return self else: return types.MethodType(self, instance)
若是要讓做爲裝飾器的類在裝飾時帶參數,就像函數裝飾器帶參同樣decorator(x,y,z)(func)
,能夠將參數定義在__init__
上進行處理,而後在__call__
中封裝一層。
class Decorator: def __init__(self, *args, **kwargs): ... do something with args ... def __call__(self, func): def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper
和函數裝飾器同樣,若是想要達到下面這種既能無參裝飾,又能帶參裝飾:
@Decorator # 無參裝飾 @Decorator(x,y,z) # 帶參裝飾 @Decorator() # 帶參裝飾,只不過沒給參數
能夠直接在__init__
上進行參數有無的判斷:
import types from functools import wraps, partial class Decorator: def __init__(self, func=None, arg1=1, arg2=2, arg3=3): # 帶參裝飾器 if func is None: self.func = partial(Decorator, arg1=arg1, arg2=arg2, arg3=arg3) else: # 無參裝飾器 wraps(func)(self) self.func = func def __call__(self, *args, **kwargs): return self.func(*args, **kwargs) def __get__(self, instance, owner): if instance is None: return self else: return types.MethodType(self, instance)
這樣的限制是裝飾器若是帶參數時,必須使用keyword方式指定參數。例如:
# 帶參裝飾普通函數,使用keywords參數方式 @Decorator(arg1=1, arg2=3, arg3=5) def func(x, y): return x + y print(func(11, 22)) print('-' * 30) # 無參裝飾普通函數 @Decorator def func1(x, y): return x + y print(func1(111, 22)) print('-' * 30) # 無參裝飾方法 class cls: @Decorator def method(self, x, y): return x + y c = cls() print(c.method(3, 4)) print('-' * 30) # 帶參裝飾方法 class cls1: @Decorator(arg1=1, arg2=3, arg3=5) def method(self, x, y): return x + y cc = cls1() print(cc.method(3, 4))
若是不考慮裝飾時是否帶參數的問題,根據上面的一大堆分析,類做爲裝飾器時的通用代碼格式以下:
import types from functools import wraps class Decorator: def __init__(self, func): wraps(func)(self) # self.func = func def __call__(self, *args, **kwargs): # return self.func(*args, **kwargs) # return self.__wrapped__(*args, **kwargs) def __get__(self, instance, owner): if instance is None: return self else: return types.MethodType(self, instance)
至於選擇self.func
的方式,仍是self.__wrapped__
的方式,隨意。
若是須要考慮裝飾時帶參數問題,那麼參考上一小節內容。
函數能夠做爲裝飾器,類也能夠做爲裝飾器。它們也都能處理處理各類需求,可是類做爲裝飾器的時候解釋了好大一堆,很是麻煩。因此,若是能夠的話,選擇函數做爲裝飾器通常會是最佳方案。