本文是裝飾器相關內容的第二篇,關於類裝飾器。
“類裝飾器”有兩種解讀方式:用來裝飾類的裝飾器;類作為裝飾器裝飾其它東西。你如何認為取決於你,兩種說法都有出現在其它的文章中。我的文章中是將”類裝飾器”解讀為第一種方式,即裝飾類的東西。而“類作為裝飾器裝飾其它東西”,我都會為其標註”類作為裝飾器”或”作為裝飾器的類”以避免歧義。
類裝飾器的形式
函式裝飾器是裝飾函式(方法)的,類裝飾器是裝飾類的,它們的表現形式是一樣的。
@decorator
class cls:
...
c = cls()
等價於:
class cls:
...
cls = decorator(cls)
c = cls()
它的效果是建立例項物件的時候,會觸發裝飾器中的程式碼邏輯。
再細細一想,發現decorator(cls)
要返回的是一個類,所以decorator中的結構大概是這樣的:
def decorator(cls):
class wrapper:
...
return wrapper
這樣就會讓被包裝的類cls實際變成wrapper類,並且以後呼叫cls構造物件的時候,實際上是呼叫wrapper類來構造物件。換句話說,wrapper已經攔截了對所有的cls操作。
但並非一定如此,比如直接返回原始的cls:
def decorator(cls):
...do something about cls...
return cls
這種方式比較簡單,本文主要對前一種方式進行詳細解釋。
由於返回的是class wrapper
,那麼它裝飾類的時候,假設所裝飾的類有構造方法__init__
,構造方法中有屬性,這個類中還有方法。如下:
@decorator
class cls(): # 等價於cls = decorator(cls)
def __init__(self, x, y):
self.attrx = x
self.attry = y
def method(self):
return self.x, self.y
那麼在包裝器wrapper中,需要能夠構造出這個物件,並且能夠取得被包裝類的物件屬性、類屬性。如下:
def decorator(cls):
class wrapper():
def __init__(self, *args, **kwargs):
self.wrapped = cls(*args, **kwargs)
def __getattr__(self, name):
return getattr(self.wrapped, name)
return wrapper
因為操作cls類的時候,實際上是在操作wrapper類。所以構造cls物件的時候:
c = cls(3, 4)
實際上是在呼叫wrapper(3, 4)
來構造物件,所以會執行wrapper裡的__init__
。但類裝飾器最終的目標是為了擴充套件類cls,所以在wrapper裡必須得構造出cls的物件。上面採取的方式是通過cls()來構造cls物件,並放在wrapper物件的一個屬性wrapped中。
因為cls已經被金蠶脫殼成了wrapper,所以要獲取到cls的屬性必須在wrapper中重寫屬性獲取的方式。
下面是一個示例:
def decorator(cls):
class wrapper():
def __init__(self, *args, **kwargs):
self.wrapped = cls(*args, **kwargs)
def __getattr__(self, name):
return getattr(self.wrapped, name)
return wrapper
@decorator
class cls():
def __init__(self, x, y):
self.attrx = x
self.attry = y
def method(self):
return self.attrx, self.attry
c = cls(3, 4)
print(c.attrx)
print(c.attry)
print(c.method())
輸出結果:
3
4
(3, 4)