元類基礎知識
元類是制造類的工廠,不過不是函數(如示例 21-2 中的
record_factory),而是類。圖 21-1 使用機器和小怪獸圖示法描述元
類,可以看出,元類是生產機器的機器。
根據 Python 對象模型,類是對象,因此類肯定是另外某個類的實例。默
認情況下,Python 中的類是 type 類的實例。也就是說,type 是大多數
內置的類和用戶定義的類的元類:
>>> 'spam'.__class__
<class 'str'>
>>> str.__class__
<class 'type'>
>>> from bulkfood_v6 import LineItem
>>> LineItem.__class__
<class 'type'>
>>> type.__class__
<class 'type'>
為了避免無限回溯,type 是其自身的實例,如最后一行所示。
注意,我沒有說 str 或 LineItem 繼承自 type。我的意思是,str 和
LineItem 是 type 的實例。這兩個類是 object 的子類。圖 21-2 可能
有助于你理清這個奇怪的現象。
object 類和 type 類之間的關系很獨特:object 是 type 的
實例,而 type 是 object 的子類。這種關系很“神奇”,無法使用
Python 代碼表述,因為定義其中一個之前另一個必須存在。type
是自身的實例這一點也很神奇。
除了 type,標準庫中還有一些別的元類,例如 ABCMeta 和 Enum。如
下述代碼片段所示,collections.Iterable 所屬的類是
abc.ABCMeta。Iterable 是抽象類,而 ABCMeta 不是——不管怎
樣,Iterable 是 ABCMeta 的實例:
>>> import collections
>>> collections.Iterable.__class__
<class 'abc.ABCMeta'>
>>> import abc
>>> abc.ABCMeta.__class__
<class 'type'>
>>> abc.ABCMeta.__mro__
(<class 'abc.ABCMeta'>, <class 'type'>, <class 'object'>)
向上追溯,ABCMeta 最終所屬的類也是 type。所有類都直接或間接地
是 type 的實例,不過只有元類同時也是 type 的子類。若想理解元
類,一定要知道這種關系:元類(如 ABCMeta)從 type 類繼承了構建
類的能力。圖 21-3 對這種至關重要的關系做了圖解。
我們要抓住的重點是,所有類都是 type 的實例,但是元類還是 type
的子類,因此可以作為制造類的工廠。具體來說,元類可以通過實現
__init__
方法定制實例。元類的 __init__
方法可以做到類裝飾器能
做的任何事情,但是作用更大,如接下來的練習所示。
理解元類計算時間的練習
我們對 21.3 節的練習做些改動,evalsupport.py 模塊與示例 21-7 一樣,
不過現在主腳本變成 evaltime_meta.py 了,如示例 21-10 所示。
示例 21-10 evaltime_meta.py:ClassFive 是 MetaAleph 元類的
實例
from evalsupport import deco_alpha
from evalsupport import MetaAleph
print('<[1]> evaltime_meta module start')
@deco_alpha
class ClassThree():print('<[2]> ClassThree body')def method_y(self):print('<[3]> ClassThree.method_y')
class ClassFour(ClassThree):print('<[4]> ClassFour body')def method_y(self):print('<[5]> ClassFour.method_y')
class ClassFive(metaclass=MetaAleph):print('<[6]> ClassFive body')def __init__(self):print('<[7]> ClassFive.__init__')def method_z(self):print('<[8]> ClassFive.method_z')
class ClassSix(ClassFive):print('<[9]> ClassSix body')def method_z(self):print('<[10]> ClassSix.method_z')
if __name__ == '__main__':print('<[11]> ClassThree tests', 30 * '.')three = ClassThree()three.method_y()print('<[12]> ClassFour tests', 30 * '.')four = ClassFour()four.method_y()print('<[13]> ClassFive tests', 30 * '.')five = ClassFive()five.method_z()print('<[14]> ClassSix tests', 30 * '.')six = ClassSix()six.method_z()
print('<[15]> evaltime_meta module end')
同樣,請拿出紙和筆,按順序寫出下述兩個場景中輸出的序號標記
<[N]>。
場景 3
在 Python 控制臺中以交互的方式導入 evaltime_meta.py 模塊。
場景 4
在命令行中運行 evaltime_meta.py 模塊。
解答和分析如下。
01. 場景3的解答
在 Python 控制臺中導入 evaltime_meta.py 模塊后得到的輸出如示例
21-11 所示。
示例 21-11 場景 3:在 Python 控制臺中導入 evaltime_meta
模塊
>>> import evaltime_meta
<[100]> evalsupport module start
<[400]> MetaAleph body
<[700]> evalsupport module end
<[1]> evaltime_meta module start
<[2]> ClassThree body
<[200]> deco_alpha
<[4]> ClassFour body
<[6]> ClassFive body
<[500]> MetaAleph.__init__ ?
<[9]> ClassSix body
<[500]> MetaAleph.__init__ ?
<[15]> evaltime_meta module end
? 與場景 1 的關鍵區別是,創建 ClassFive 時調用了
MetaAleph.__init__
方法。
? 創建 ClassFive 的子類 ClassSix 時也調用了
MetaAleph.__init__
方法。
Python 解釋器計算 ClassFive 類的定義體時沒有調用 type 構建具
體的類定義體,而是調用 MetaAleph 類。看一下示例 21-12 中定
義的 MetaAleph 類,你會發現 __init__
方法有四個參數。
self
這是要初始化的類對象(例如 ClassFive)。
name、bases、dic
與構建類時傳給 type 的參數一樣。
示例 21-12 evalsupport.py:定義 MetaAleph 元類,摘自示例
21-7
class MetaAleph(type):print('<[400]> MetaAleph body')def __init__(cls, name, bases, dic):print('<[500]> MetaAleph.__init__')def inner_2(self):print('<[600]> MetaAleph.__init__:inner_2')cls.method_z = inner_2
編寫元類時,通常會把 self 參數改成 cls。例如,在上
述元類的 __init__
方法中,把第一個參數命名為 cls 能清楚
地表明要構建的實例是類。
__init__
方法的定義體中定義了 inner_2 函數,然后將其綁定給
cls.method_z。MetaAleph.__init__
方法簽名中的 cls 指代要
創建的類(例如 ClassFive)。而 inner_2 函數簽名中的 self
最終是指代我們在創建的類的實例(例如 ClassFive 類的實
例)。
場景4的解答
在命令行中運行 python3 evaltime_meta.py 命令后得到的輸出
如示例 21-13 所示。
示例 21-13 場景 4:在 shell 中運行 evaltime_meta.py
$ python3 evaltime.py
<[100]> evalsupport module start
<[400]> MetaAleph body
<[700]> evalsupport module end
<[1]> evaltime_meta module start
<[2]> ClassThree body
<[200]> deco_alpha
<[4]> ClassFour body
<[6]> ClassFive body
<[500]> MetaAleph.__init__
<[9]> ClassSix body
<[500]> MetaAleph.__init__
<[11]> ClassThree tests ..............................
<[300]> deco_alpha:inner_1 ?
<[12]> ClassFour tests ..............................
<[5]> ClassFour.method_y ?
<[13]> ClassFive tests ..............................
<[7]> ClassFive.__init__
<[600]> MetaAleph.__init__:inner_2 ?
<[14]> ClassSix tests ..............................
<[7]> ClassFive.__init__
<[600]> MetaAleph.__init__:inner_2 ?
<[15]> evaltime_meta module end
? 裝飾器依附到 ClassThree 類上之后,method_y 方法被替換成
inner_1 方法……
? 雖然 ClassFour 是 ClassThree 的子類,但是沒有依附裝飾器
的 ClassFour 類卻不受影響。
? MetaAleph 類的 __init__
方法把 ClassFive.method_z 方法
替換成 inner_2 函數。
? ClassFive 的子類 ClassSix 也是一樣,method_z 方法被替換
成 inner_2 函數。
注意,ClassSix 類沒有直接引用 MetaAleph 類,但是卻受到了影
響,因為它是 ClassFive 的子類,進而也是 MetaAleph 類的實
例,所以由 MetaAleph.__init__
方法初始化。
如果想進一步定制類,可以在元類中實現 __new__
方
法。不過,通常情況下實現 __init__
方法就夠了。
現在,我們可以實踐這些理論了。我們將創建一個元類,讓描述符
以最佳的方式自動創建儲存屬性的名稱。