python3元類深刻解讀

因爲 oschina 的博客不支持 mermaid 的圖,能夠看看掛在gitee上的靜態博客html

0. intro

元類是 python 裏被說爛了的一個東西,然而平常用到的地方實在很少,每次想到都得查一下谷歌,想一想幹脆在博客留個筆記好了。python

元類的主要用途是定製的產生過程,以便於根據類聲明包含的信息來建立出不一樣的類。git

1. type

提到元類不得不說一下 python 的類型系統。app

python 的 class 也被視做一個對象,定製一個 class 的構造過程其實就和平時在 class 定義裏寫__init__沒啥區別。函數

python3 裏類的類型是typetype又繼承自objectobject的父類是本身,構成一個奇怪的閉環。其中,type自己是一個特殊的類,他是本身的實例。spa

graph TB;
	type --> |inherite|object;
	type --> |instance-of| type;
	object --> |instance-of|type;
	other-cls --> |instance-of| type;
	other-cls --> |inherite| object;
	other-cls-instance --> |instance-of|other-cls;

type有兩種調用方式,一種是最經常使用的接受一個對象參數,返回該對象的類型,另外一種是不怎麼經常使用的,直接建立一個新的類型。翻譯

# usage with one argument
type(object) # 返回對象的類型,這裏返回的是 `type`

# usage with three arguments
type(name, bases, attr) # 返回新建立的類型

2. meta class

元類語法以下code

class MyClass(basecls1, basecls2, metaclass=MetaClass, named1=arg, named2=arg): ...

通常的元類能夠是一個真正的class或者一個函數。htm

以函數爲例:對象

def meta_f(name, bases, attr):
	return type(name, bases, attr)

class A(metaclass=meta_f): ...

以類爲例:

class MetaC(type):
	def __new__(mcs, name, bases, attr):
		return type.__new__(mcs, name, bases, attr)

class A(metaclass=MetaC): ...

元類能夠接受參數,參數必須是命名的,傳遞參數的方式是寫在類聲明的繼承列表裏。

def meta(name, bases, attr, named_arg, optional_arg=None):
	return type(name, bases, dict(**attr, arg=named_arg, option=optional_arg))

class A(metaclass=meta, named_arg="hi"): ...

print(A.arg)  # output: hi

位置參數都會被當成繼承列表,做爲bases參數(list)的一部分傳入元類。

3. 元類繼承規則

有了元類那麼就有了相應繼承規則,顯而易見。元類用於構造一個類,兩個父類分別有一個不一樣的元類顯然會形成衝突:這個子類用哪一個元類構造?

首先看元類的在建立類的過程當中的位置,摘自 python 文檔3.3.3.1. Metaclasses

  • MRO entries are resolved
  • the appropriate metaclass is determined
  • the class namespace is prepared
  • the class body is executed
  • the class object is created

一旦處理完繼承鏈(mro, method resolve order)以後,就會決定採用哪一個 metaclass 做爲構造這個類的元類。

在 python 文檔的3.3.3.3 determining the appropriate metaclass中描述瞭如何肯定合適的元類,摘錄以下。

  • if no bases and no explicit metaclass are given, then type() is used
  • if an explicit metaclass is given and it is not an instance of type(), then it is used directly as the metaclass
  • if an instance of type() is given as the explicit metaclass, or bases are defined, then the most derived metaclass is used

翻譯以下

  • 若是沒有基類也沒有指定 metaclass,那麼type()將做爲元類使用。
  • 若是指定了元類,而且該元類不是 type 的實例,那麼直接使用這個元類。
  • 若是元類是一個 type 的實例,或者存在基類,那麼使用最衍生的元類。

有一個比較難理解的點是

most derived metaclass

也就是所謂的最衍生的元類。慣例,先放文檔解釋

The most derived metaclass is selected from the explicitly specified metaclass (if any) and the metaclasses (i.e. type(cls)) of all specified base classes. The most derived metaclass is one which is a subtype of all of these candidate metaclasses. If none of the candidate metaclasses meets that criterion, then the class definition will fail with TypeError.

簡單翻譯以下

最衍生的元類會從類聲明中明確提供的元類,還有全部明確繼承的基類的元類中選擇。最衍生的元類是以上全部候選元類的子類型,若是沒有類型符合這一條件,則拋出TypeError異常。

重點在於,最衍生的元類必須是,全部繼承的基類的元類和指定元類的子類型

在這裏提醒一下,issubclass(cls, cls)的結果是True。換句話說,必須有一個類是全部元類的子類,或者全部基類有相同的元類。

代碼舉例以下

class MetaA(type):
    def __new__(mcs, name, bases, attr):
        print('MetaA <- '+name)
        return type.__new__(mcs, name, bases, attr)

class MetaB(type):
    def __new__(mcs, name, bases, attr):
        print('MetaB <- '+name)
        return type.__new__(mcs, name, bases, attr)

class BaseA: ...
class BaseB(metaclass=MetaA): ...
class BaseC(metaclass=MetaB): ...

# 未指定元類,基類元類分別是type和type的子類,則選擇繼承鏈底部的那個類
class A(BaseA, BaseB): ...  # Ok,元類是 MetaA

# 指定元類,元類和基類元類相同的狀況下,元類就是那個元類
class C(BaseB, metaclass=MetaA): ...  # Ok,元類是 MetaA

# 指定元類,元類並不處於繼承鏈底端的狀況下,元類選擇繼承鏈底端的類
class D(BaseB, metaclass=type): ...  # Ok,元類是 MetaA

# 指定元類,但元類和父類無父子類關係
class E(BaseC, metaclass=MetaA): ...  # TypeError

# 不指定元類,基類具備不一樣的元類
class F(BaseA,BaseB,BaseC): ...  # TypeError

輸出以下

MetaA <- A
MetaA <- C
MetaA <- D

In [71]: class E(BaseC, metaclass=MetaA): ...  # TypeError
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-71-9129a36c52b2> in <module>
----> 1 class E(BaseC, metaclass=MetaA): ...  # TypeError

TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

In [72]: class F(BaseA,BaseB,BaseC): ...  # TypeError
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-72-1c510edd69d1> in <module>
----> 1 class F(BaseA,BaseB,BaseC): ...  # TypeError

TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

但元類是函數的狀況下會有比較特殊的表現,注意規則二。

  • 若是指定了元類,而且該元類不是 type 的實例,那麼直接使用這個元類。

若是函數形式的元類做爲父類的元類時不會列入選擇,除非指定當前類的元類爲函數,纔會調用函數形式的元類,並且是無條件選擇這個函數形式的元類。

def MetaA(name, bases, attr):
    print("MetaA <- "+name)
    return type(name, bases, attr)

class MetaB(type):
    def __new__(mcs, name, bases, attr):
        return type.__new__(mcs, name, bases, attr)

class A(MetaB, metaclass=MetaA): ...  # Ok,無條件選擇元類 MetaA
相關文章
相關標籤/搜索