天天看點

python裝飾器class_python裝飾器2:類裝飾器

本文是裝飾器相關内容的第二篇,關于類裝飾器。

"類裝飾器"有兩種解讀方式:用來裝飾類的裝飾器;類作為裝飾器裝飾其它東西。你如何認為取決于你,兩種說法都有出現在其它的文章中。我的文章中是将"類裝飾器"解讀為第一種方式,即裝飾類的東西。而“類作為裝飾器裝飾其它東西”,我都會為其标注"類作為裝飾器"或"作為裝飾器的類"以避免歧義。

類裝飾器的形式

函數裝飾器是裝飾函數(方法)的,類裝飾器是裝飾類的,它們的表現形式是一樣的。

@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)