Python 元類

印象中,是在建立單例模式時知道能夠用到元類(metaclass),但始終對其瞭解的不是很透徹,不少人也都說元類是Python中較難理解的概念之一,因而找來幾本書,但願能夠找到答案,本文以Python3爲例。python

本文參考:設計模式

《人人都懂設計模式》函數

《Python Cookbook》設計

《 流暢的Python》code

先來簡單介紹下:元類(metaclass)是一個類,你也能夠理解爲類的類,由於Python中的類是在運行時動態建立的,那麼經過元類即可以控制類屬性和類實例的建立過程。對象

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

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類繼承了構建類的能力。it

咱們一般用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做爲此類的元類。

以上。

相關文章
相關標籤/搜索