天天看點

Python metaclass 簡介

metaclass

譯為

元類

,可以了解為 metaclass 為描述類的超類,同時可以改變子類的形态,本文介紹相關内容。

Python中的類

  • class,對于大部分面向對象語言來說,class是一段定義了如何産生object的代碼塊。在Python中這一定義也成立。
  • 但是在Python中,class并不隻有這一角色。class實際上也是object。當我們使用class定義一個類的時候,Python會執行相應代碼并在記憶體中建立一個對應類的的object。
  • 而metaclass 是控制類執行個體(這裡表示類本身的定義,不是類的執行個體)産生的”超類“
直接看一個 metaclass的例子可以有更直覺的了解

metaclass 執行個體

class Mymeta(type):
    def __init__(self, name, bases, dic):
        super().__init__(name, bases, dic)
        print('Mymeta.__init__')
        print(self.__name__)
        print(dic)
        print(self.yaml_tag)

    def __new__(cls, *args, **kwargs):
        print('Mymeta.__new__')
        print(cls.__name__)
        return type.__new__(cls, *args, **kwargs)

    def __call__(cls, *args, **kwargs):
        print('Mymeta.__call__')
        obj = cls.__new__(cls)
        cls.__init__(cls, *args, **kwargs)
        return obj


class Foo(metaclass=Mymeta):
    yaml_tag = '!Foo'

    def __init__(self, name):
        print('Foo.__init__')
        self.name = name

    def __new__(cls, *args, **kwargs):
        print('Foo.__new__')
        return object.__new__(cls)

if __name__ == '__main__':
    foo = Foo('foo')
           

複制

  • 輸出
Mymeta.__new__
Mymeta
Mymeta.__init__
Foo
{'__module__': '__main__', '__qualname__': 'Foo', 'yaml_tag': '!Foo', '__init__': <function Foo.__init__ at 0x000002626354E550>, '__new__': <function Foo.__new__ at 0x000002626354E5E0>}
!Foo
Mymeta.__call__
Foo.__new__
Foo.__init__           

複制

  • 從上面的運作結果可以發現在定義 class Foo() 定義時,會依次調用 MyMeta 的

    __new__

    __init__

    方法建構 Foo 類,然後在調用 foo = Foo() 建立類的執行個體對象時,才會調用 MyMeta 的

    __call__

    方法來調用 Foo 類的

    __new__

    __init__

    方法。
在沒有 metaclass 的情況下,子類繼承父類,父類是無法對子類執行操作的,但有了 metaclass,就可以對子類進行操作,就像裝飾器那樣可以動态定制和修改被裝飾的類,metaclass 可以動态的定制或修改繼承它的子類。

meatclass 工作原理

類是 type 類的執行個體

class TestClass:
    pass

if __name__ == '__main__':
    print(type(TestClass))
    print(isinstance(TestClass, type))           

複制

  • 輸出
<class 'type'>
True           

複制

  • 可以看到類是 type 類的執行個體

使用者定義類,是 type 類的

__call__

運算符重載

當我們定義一個類的語句結束時,真正發生的情況,是 Python 調用 type 的

__call__

運算符。

當你定義一個類時,寫成下面這樣時:

class MyClass:
    data = 1           

複制

Python 真正執行的是下面這段代碼:

class = type(classname, superclasses, attributedict)           

複制

這裡等号右邊的 type(classname, superclasses, attributedict),就是 type 的

__call__

運算符重載,它會進一步調用:

type.__new__(typeclass, classname, superclasses, attributedict)
type.__init__(class, classname, superclasses, attributedict)           

複制

metaclass 是 type 的子類,通過替換 type 的

__call__

運算符重載機制,“超越變形”正常的類

正是 Python 的類建立機制,給了 metaclass 大展身手的機會。一旦你把一個類型 MyClass 的 metaclass 設定成 MyMeta,MyClass 就不再由原生的 type 建立,而是會調用 MyMeta 的

__call__

運算符重載。

class = type(classname, superclasses, attributedict) 
# 變為了
class = MyMeta(classname, superclasses, attributedict)           

複制

metaclass 使用方法

定義屬性

當定義class的時候,我們可以使用

__metaclass__

attribute來指定用來初始化目前class的metaclass。如下面的例子所示:

class Foo(object):
    __metaclass__ = something
    [other statements...]           

複制

  • 如果我們指定了

    __metaclass__

    ,Python就是使用這個metaclass來生成class

    Foo

  • 當Python試圖建立class

    Foo

    的時候,Python會首先在class的定義中尋找

    __metaclass__

    attribute。如果存在

    __metaclass__

    ,Python将會使用指定的

    __metaclass__

    來建立class

    Foo

    。如果沒有指定的話,Python就會使用預設的

    type

    作為metaclass建立

    Foo

顯式指定 metaclass

在建立類時顯式指定 metaclass 類的内容

class Foo(metaclass=Mymeta):
	pass           

複制

  • 該方法指定的metaclass會被子類繼承
  • 也就是說指定 metaclass 之後,Foo 類及其子類在預設情況下都會使用 Mymeta 作為 metaclass
  • 這就是控制子類行為的方法

繼承父類 metaclass

對于下面這個例子:

class Foo(Bar):
    pass           

複制

  • Python首先在

    Foo

    中尋找是否存在

    __metaclass__

    attribute。
  • 如果存在的話,Python将使用這個metaclass在記憶體中建立一個名字為

    Foo

    的class object。如果Python
  • 如果class定義中不存在

    __metaclass__

    的話,Python将會尋找MODULE級别的

    __metaclass__

    。如果存在的話就進行與前述相同的操作。
  • 當Python仍然沒有找到

    __metaclass__

    時,Python将會使用目前類的父類的metaclass來建立目前類。在我們上面這個例子中,Python會使用

    Foo

    的父類

    Bar

    的metaclass來建立

    Foo

    的class object。
同時需要注意的是,在class中定義的

__metaclass__

attribute并不會被子類繼承。被子類繼承的是母類的metaclass,也就是母類的

.__class__

attribute。比如上面的例子中,

Bar.__class__

将會被

Foo

繼承。也就是說,如果

Bar

定義了一個

__metaclass__

attribute來使用

type()

建立

Bar

的class object(而非使用

type.__new__()

),那麼

Bar

的子類,也就是

Foo

,并不會繼承這一行為。

使用 metaclass 的風險

不過,凡事有利必有弊,尤其是 metaclass 這樣“逆天”的存在。正如你所看到的那樣,metaclass 會"扭曲變形"正常的 Python 類型模型。是以,如果使用不慎,對于整個代碼庫造成的風險是不可估量的。

換句話說,metaclass 僅僅是給小部分 Python 開發者,在開發架構層面的 Python 庫時使用的。而在應用層,metaclass 往往不是很好的選擇。

參考資料

  • https://zhuanlan.zhihu.com/p/98440398
  • https://www.jianshu.com/p/224ffcb8e73e