譯為
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__
方法建構 Foo 類,然後在調用 foo = Foo() 建立類的執行個體對象時,才會調用 MyMeta 的__init__
方法來調用 Foo 類的__call__
和__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__
運算符重載
__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__
運算符重載機制,“超越變形”正常的類
__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...]
複制
- 如果我們指定了
,Python就是使用這個metaclass來生成class__metaclass__
。Foo
- 當Python試圖建立class
的時候,Python會首先在class的定義中尋找Foo
attribute。如果存在__metaclass__
,Python将會使用指定的__metaclass__
來建立class__metaclass__
。如果沒有指定的話,Python就會使用預設的Foo
作為metaclass建立type
。Foo
顯式指定 metaclass
在建立類時顯式指定 metaclass 類的内容
class Foo(metaclass=Mymeta):
pass
複制
- 該方法指定的metaclass會被子類繼承
- 也就是說指定 metaclass 之後,Foo 類及其子類在預設情況下都會使用 Mymeta 作為 metaclass
- 這就是控制子類行為的方法
繼承父類 metaclass
對于下面這個例子:
class Foo(Bar):
pass
複制
- Python首先在
中尋找是否存在Foo
attribute。__metaclass__
- 如果存在的話,Python将使用這個metaclass在記憶體中建立一個名字為
的class object。如果PythonFoo
- 如果class定義中不存在
的話,Python将會尋找MODULE級别的__metaclass__
。如果存在的話就進行與前述相同的操作。__metaclass__
- 當Python仍然沒有找到
時,Python将會使用目前類的父類的metaclass來建立目前類。在我們上面這個例子中,Python會使用__metaclass__
的父類Foo
的metaclass來建立Bar
的class object。Foo
同時需要注意的是,在class中定義的attribute并不會被子類繼承。被子類繼承的是母類的metaclass,也就是母類的
__metaclass__
attribute。比如上面的例子中,
.__class__
将會被
Bar.__class__
繼承。也就是說,如果
Foo
定義了一個
Bar
attribute來使用
__metaclass__
建立
type()
的class object(而非使用
Bar
),那麼
type.__new__()
的子類,也就是
Bar
,并不會繼承這一行為。
Foo
使用 metaclass 的風險
不過,凡事有利必有弊,尤其是 metaclass 這樣“逆天”的存在。正如你所看到的那樣,metaclass 會"扭曲變形"正常的 Python 類型模型。是以,如果使用不慎,對于整個代碼庫造成的風險是不可估量的。
換句話說,metaclass 僅僅是給小部分 Python 開發者,在開發架構層面的 Python 庫時使用的。而在應用層,metaclass 往往不是很好的選擇。
參考資料
- https://zhuanlan.zhihu.com/p/98440398
- https://www.jianshu.com/p/224ffcb8e73e