Python 元類

印象中,是在創建單例模式時知道可以用到元類(metaclass),但始終對其了解的不是很透徹,很多人也都說元類是Python中較難理解的概念之一,於是找來幾本書,希望可以找到答案,本文以Python3為例。

本文參考:

《人人都懂設計模式》

《Python Cookbook》

《 流暢的Python》

先來簡單介紹下:元類(metaclass)是一個類,你也可以理解為類的類,因為Python中的類是在運行時動態創建的,那麼通過元類便可以控制類屬性和類實例的創建過程。

來看看用元類實現的單例模式:

class Singleton(type):
    """
    單例模式
    """
    def __init__(cls, *args, **kwargs):
        cls.__instance = None
        super().__init__(*args, **kwargs)

    def __call__(cls, *args, **kwargs):
        if cls.__instance is None:
            cls.__instance = super().__call__(*args, **kwargs)
        return cls.__instance


class Test(metaclass=Singleton):

    def __init__(self):
        pass


a = Test()
b = Test()
print(id(a))
print(id(b))

具體的實現是:創建類時顯式的指定類的metaclass,而自定義的metaclass繼承type,並重新實現__call__方法。

於是,有了兩個問題:

  • 為什麼自定義的metaclass繼承type?

因為,在Python中,type是默認的metaclass(內建元類),Python允許我們自定義metaclass,自定義的metaclass必須繼承自type,也就是:元類從type類繼承了構建類的能力。

我們通常用type來獲取對象所屬的類,就像這樣:

In [10]: a = 10

In [11]: type(a)
Out[11]: int

然而,type還是一個類,你可以通過type來新建一個類,看type的源碼,通過type(name, bases, dict)便可以生成一個新的類:

In [44]: test_class = type('Test', (), dict({'name': None}))

In [45]: a = test_class()

In [46]: a.name = 'Tony'

In [47]: a.name
Out[47]: 'Tony'

默認情況下,Python中類都是type類的實例:

In [12]: class A:
    ...:     pass
    ...:

In [13]: A.__class__
Out[13]: type

In [14]: int.__class__
Out[14]: type

當你使用class關鍵字時,Python在幕後做的事情,就是通過元類來實現的。

  • 為什麼重新定義__call__方法?

提出該問題是因為,與Python類創建相關的方法是:

__new__:類方法,負責對象的創建,在定義類時需要返回一個實例,在我們通過類名進行實例化對象時自動調用。
__init__:初始化函數,負責對new實例化的對象進行初始化,負責對象狀態的更新和屬性的設置,在每一次實例化對象之後調用。

而我們常用__call__方法只是為了聲明這個類的對象是可調用的(callable)。

但是,在metaclass中__call__方法還負責對象的創建,這就是為什麼要重新定義的原因了。

重定義了__call__方法之後,一個對象的創建過程大概如下圖:

我們驗證一下:

class TestMetaClass(type):

    def __init__(cls, what, bases=None, dict=None):
        print("metaclass init")
        super().__init__(what, bases, dict)

    def __call__(cls, *args, **kwargs):
        print("metaclass call")
        self = super(TestMetaClass, cls).__call__(*args, **kwargs)
        return self


class TestClass(metaclass=TestMetaClass):

    def __init__(self, *args, **kwargs):
        print("class init")
        super().__init__()

    def __new__(cls, *args, **kwargs):
        print("class new")
        self = super().__new__(cls)
        return self

a = TestClass()

返回:

metaclass init
metaclass call
class new
class init

可以看到,__call__方法在類執行__new____init__之前執行,這樣就可以解釋:

在Singleton中的__call__方法對類屬性__instance進行判斷:

  1. 如果__instance為None,表明類還未進行實例化,那麼給__instance賦值為元類的父類(type)的__call__方法。
  2. 如果__instance不為None,說明類已經進行過實例化,直接返回cls.__instance中的類實例。

便實現了單例模式。

除了重新定義__call__以外,元類可以通過實現__init__方法來訂製實例,元類的__init__方法可以做到類裝飾器能做到的任務事情,並且作用更大。

如果想要進一步訂製類,可以在元類中實現__new__方法。

另,編寫元類時,通常會把self參數改為cls,這樣能更清楚的表明要構建的實例是類。

元類的調用

上述例子中,都是通過metaclass=''來設置類的元類,還可以這樣:

class TestClass():
    __metaclass__ = TestMetaClass

    def __init__(self, *args, **kwargs):
        print("class init")
        super().__init__()

在執行類定義時,解釋器會先尋找這個類屬性中的__metaclass__,如果此屬性存在,就將這個屬性賦值給此類作為它的元類,如果此屬性沒有定義的話,就會向上查找父類的__metaclass__,如果沒有發現任何的父類,並且解釋器中也沒有名字為__metaclass__的全局變數,這個類就是傳統類,會使用type.ClassType作為此類的元類。

以上。

Tags: