Python MetaClass元類實現的底層原理

2020-07-16 10:05:02
要理解 MetaClass 的底層原理,首先要深入理解 Python 型別模型。本節將從以下 2 點對 Python 型別模型做詳細的介紹。

1) 所有的 Python 的使用者定義類,都是 type 這個類的範例

事實上,類本身不過是一個名為 type 類的範例,可以通過如下程式碼進行驗證:
class MyClass:
  pass

instance = MyClass()
print(type(instance))
print(type(MyClass))
輸出結果為:

<class '__main__.MyClass'>
<class 'type'>

可以看到,instance 是 MyClass 的範例,而 MyClass 是 type 的範例。

2) 使用者自定義類,只不過是 type 類的 __call__ 運算子過載。

當定義完成一個類時,真正發生的情況是 Python 會呼叫 type 類的 __call__ 運算子。

簡單來說,當定義一個類時,例如下面語句:
class MyClass:
  data = 1
Python 底層執行的是下面這段程式碼:
class = type(classname, superclasses, attributedict)
其中等號右邊的 type(classname, superclasses, attributedict) 就是 type 的 __call__ 運算子過載,它會進一步呼叫下面這 2 個函數:
type.__new__(typeclass, classname, superclasses, attributedict)
type.__init__(class, classname, superclasses, attributedict)
以上整個過程,可以通過如下程式碼進行論證:
class MyClass:
  data = 1
 
instance = MyClass()
print(MyClass,instance)
print(instance.data)
MyClass = type('MyClass', (), {'data': 1})
instance = MyClass()

print(MyClass,instance)
print(instance.data)
執行結果為:

<class '__main__.MyClass'> <__main__.MyClass object at 0x000001CB469F7400>
1
<class '__main__.MyClass'> <__main__.MyClass object at 0x000001CB46A50828>
1

由此可見,正常的 MyClass 定義,和手工呼叫 type 運算子的結果是完全一樣的。

總之,正是 Python 的類建立機制,給了 metaclass 大展身手的機會,即一旦把一個型別 MyClass 設定成元類 MyMeta,那麼它就不再由原生的 type 建立,而是會呼叫 MyMeta 的 __call__ 運算子過載:
class = type(classname, superclasses, attributedict)
# 變為了
class = MyMeta(classname, superclasses, attributedict)

使用 metaclass 的風險

正如上面所看到的那樣,metaclass 這樣“逆天”的存在,會"扭曲變形"正常的 Python 型別模型,所以,如果使用不慎,對於整個程式碼庫造成的風險是不可估量的。

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

建議初學者不要輕易嘗試使用 mateclass。