內置類對象雖然在底層靜態定義好了,但是還不夠完善。解釋器在啓動之後還要再打磨一下,然後才能得到我們平時使用的類型對象,而這個過程被稱爲類型對象的初始化。
類型對象的初始化,是通過 PyType_Ready 函數實現的,我們來看一下,它位於 Objects/typeobject.c 中。另外由於初始化這部分內容比較多,接下來我們準備用三篇文章去介紹它。
int PyType_Ready(PyTypeObject *type) { //這裏的參數顯然是類型對象 //以 <class 'type'> 爲例 //dict:屬性字典 //bases:繼承的所有基類,即 __bases__ PyObject *dict, *bases; //base:繼承的第一個基類,即 __base__ PyTypeObject *base; Py_ssize_t i, n; //...... //...... //獲取類型對象中 tp_base 成員指定的基類 base = type->tp_base; if (base == NULL && type != &PyBaseObject_Type) { //如果基類爲空、並且該類本身不是object //那麼將該類的基類設置爲 object、即 &PyBaseObject_Type //所以一些類型對象在底層定義的時候,tp_base 成員爲空 //因爲tp_base是在這裏、也就是初始化的時候進行設置的 base = type->tp_base = &PyBaseObject_Type; Py_INCREF(base); } //如果基類不是NULL,也就是指定了基類 //但是基類的屬性字典是NULL if (base != NULL && base->tp_dict == NULL) { //說明該類的基類尚未初始化,那麼會先對基類進行初始化 //注意這裏的 tp_dict,它表示每個類都會有的屬性字典 //而屬性字典是否爲 NULL,是類型對象是否初始化完成的重要標誌 if (PyType_Ready(base) < 0) goto error; } //如果該類型對象的 ob_type 爲空,但是基類不爲空 //那麼將該類型對象的 ob_type 設置爲基類的 ob_type //爲什麼要做這一步, 我們後面會詳細說 if (Py_TYPE(type) == NULL && base != NULL) Py_TYPE(type) = Py_TYPE(base); //獲取 __bases__,檢測是否爲空 bases = type->tp_bases; //如果爲空,則根據 __base__ 進行設置 if (bases == NULL) { //如果 base 也爲空,那麼 bases 就是空元祖 //而base如果爲空了,說明當前的類對象一定是object if (base == NULL) bases = PyTuple_New(0); //如果 base 不爲空,那麼 bases 就是 (base,) else bases = PyTuple_Pack(1, base); if (bases == NULL) goto error; //設置 tp_bases type->tp_bases = bases; } //設置屬性字典,後續再聊 dict = type->tp_dict; if (dict == NULL) { dict = PyDict_New(); if (dict == NULL) goto error; type->tp_dict = dict; } //...... }
對於指定了tb_base的類對象,當然就使用指定的基類,而對於沒有指定tp_base的類對象,虛擬機將爲其指定一個默認的基類:&PyBaseObject_Type ,也就是 Python 的 object。
現在我們看到 PyType_Type 的 tp_base 指向了 PyBaseObject_Type,這在Python中體現的就是 type 繼承自 object、或者說 object 是 type 的父類。但是所有的類的 ob_type 又都指向了 PyType_Type,包括 object,因此我們又說 type 是包括 object 在內的所有類的類(元類)。
而在獲得了基類之後,會判斷基類是否被初始化,如果沒有,則需要先對基類進行初始化。可以看到,判斷初始化是否完成的條件是tp_dict是否爲NULL,這符合之前的描述。對於內置類對象來說,在解釋器啓動的時候,就已經作爲全局對象存在了,所以它們的初始化不需要做太多工作,只需小小的完善一下即可,比如設置基類、類型、以及對 tp_dict 進行填充。
在基類設置完畢後,會繼續設置 ob_type,這個ob_type就是 class 返回的類型對象。
首先 PyType_Ready 函數裏面接收的是一個 PyTypeObject 對象,我們知道這個在 Python 中就是類對象。因此這裏是設置這些類對象的 ob_type,那麼對應的顯然就是元類(metaclass),我們自然會想象到Python的type。
而Py_TYPE(type) = Py_TYPE(base)這一行代碼是把父類的ob_type設置成了當前類的ob_type,那麼這一步的意義何在呢?我們使用Python來演示一下。
class MyType(type):
pass
class A(metaclass=MyType):
pass
class B(A):
pass
print(type(A)) # <class '__main__.MyType'>
print(type(B)) # <class '__main__.MyType'>
我們看到B繼承了A,而A的類型是MyType,那麼B的類型也成了MyType。也就是說 A 是由 XX 生成的,那麼B在繼承A之後,B 也會由 XX 生成,所以源碼中的那一步就是用來做這件事情的。另外,這裏之所以用 XX 代替,是因爲 Python 裏面不僅僅只有 type 是元類,那些繼承了 type 的子類也可以是元類。
而且如果你熟悉 flask 的話,你會發現 flask 源碼裏面就有類似於這樣的操作:
class MyType(type): def __new__(mcs, name, bases, attrs): # 關於第一個參數我們需要說一下 # 對於一般的類來說這裏應該是cls # 但我們這裏是元類,所以應該用mcs,意思就是metaclass # 我們額外設置一些屬性吧,關於元類我們後續會介紹 # 雖然目前還沒有看底層實現,但至少使用方法應該知道 attrs.update({"name": "古明地覺"}) return super().__new__(mcs, name, bases, attrs) def with_metaclass(meta, bases=(object, )): return meta("", bases, {}) class Girl(with_metaclass(MyType, (int,))): pass print(type(Girl)) # <class '__main__.MyType'> print(getattr(Girl, "name")) # 古明地覺 print(Girl("123")) # 123
所以邏輯很清晰了,虛擬機就是將基類的metaclass設置爲子類的metaclass。對於當前的PyType_Type來說,其metaclass就是object的metaclass,也是它自己。而在源碼的PyBaseObject_Type中也可以看到,其ob_type被設置成了 &PyType_Type。
if (Py_TYPE(type) == NULL && base != NULL) Py_TYPE(type) = Py_TYPE(base);
tb_base 和 ob_type 設置完畢之後,會設置 tb_bases。tb_base 對應 __base__,tb_bases 對應 __bases__,我們用 Python 演示一下,這兩者的區別。
class A:
pass
class B(A):
pass
class C:
pass
class D(B, C):
pass
print(D.__base__) # <class '__main__.B'>
print(D.__bases__) # (<class '__main__.B'>, <class '__main__.C'>)
print(C.__base__) # <class 'object'>
print(C.__bases__) # (<class 'object'>,)
print(B.__base__) # <class '__main__.A'>
print(B.__bases__) # (<class '__main__.A'>,)
我們看到 D 同時繼承多個類,那麼 tp_base 就是先出現的那個基類。而 tp_bases 則是繼承的所有基類,但是基類的基類是不會出現的,比如 object。對於 B 而言也是一樣的。
然後我們看看 C,因爲 C 沒有顯式地繼承任何類,那麼 tp_bases 就是NULL。但是Python3裏面所有的類都默認繼承了object,所以tp_base就是object。而 tp_bases,顯然是 (object,)。
以上就是 tp_base、ob_type、tp_bases 的設置,還是比較簡單的,它們在設置完畢之後,就要對 tp_dict 進行填充了。而填充 tp_dict 是一個極其繁複的過程,我們下一篇文章再說。
以上就是本次分享的所有內容,想要了解更多歡迎前往公衆號:Python編程學習圈,每日干貨分享