通過 Type 和 Object 之間的關聯,進一步分析類型對象
楔子
type 和 object 兩者的關系估計會讓很多人感到困惑,我們說 type 站在類型金字塔的頂端,任何對象按照類型追根溯源,最終得到的都是 type。而 object 站在繼承金字塔的頂端,任何類型對象按照繼承關系追根溯源,最終得到的都是 object。
因此我們可以得出以下結論:
- type 的父類是 object
- object 的類型是 type
驗證一下:
print(type.__base__) # <class 'object'>
print(object.__class__) # <class 'type'>
打印結果說明結論正確,但這就奇怪了,type 的父類是 object,而 object 的類型又是 type,那么問題來了,是先有 type 還是先有 object 呢?帶著這些疑問,開始下面的內容。
類是由誰創(chuàng)建的
首先必須要澄清一個事實,類對象的類型是 type,這句話是沒有問題的。但如果說類對象都是由 type 創(chuàng)建的,就有些爭議了。因為 type 能夠創(chuàng)建的是自定義的類,而內置的類在底層是預先定義好的。
# int、tuple、dict 等內置類型
# 在底層是預先定義好的,以全局變量的形式存在
# 我們直接就可以拿來用
print(int) # <class 'int'>
print(tuple) # <class 'tuple'>
# 但對于自定義的類,顯然就需要在運行時動態(tài)創(chuàng)建了
# 而創(chuàng)建這一過程,就交給 type 來做
class Girl:
pass
然后 type 也只能對自定義類進行屬性上的增刪改,內置的類則不行。
class Girl:
pass
# 給類對象增加一個成員函數
type.__setattr__(
Girl,
"info",
lambda self: "name: 古明地覺, age: 17"
)
# 實例化之后就可以調用了
print(Girl().info()) # name: 古明地覺, age: 17
# 但內置的類對象,type 是無法修改的
try:
type.__setattr__(int, "a", "b")
except TypeError as e:
print(e)
"""
TypeError: cannot set 'a' attribute of immutable type 'int'
"""
上一篇文章中我們說了,Python 所有的類型對象(包括 type)都是由 PyTypeObject 結構體實例化得到的,只不過結構體字段的值不同,得到的類也不同。并且內置的類型對象在底層是預定義好的,它們在解釋器看來是同級別的,不存在誰創(chuàng)建誰。
而每一個對象都有引用計數和類型,然后解釋器將這些類對象的類型都設置成了 type,我們舉例說明。不過在此之前,需要先說一個宏。
// Include/object.h
// _PyObject_EXTRA_INIT 可以忽略掉
// 然后我們看到這個宏是用來初始化引用計數和類型的
// 并且引用計數的值為 uint32 類型的最大值,因此創(chuàng)建的是永恒對象
#define PyObject_HEAD_INIT(type) \
{ \
_PyObject_EXTRA_INIT \
{ _Py_IMMORTAL_REFCNT }, \
(type) \
},
// 用于初始化引用計數、類型和 ob_size
#define PyVarObject_HEAD_INIT(type, size) \
{ \
PyObject_HEAD_INIT(type) \
(size) \
},
下面我們來看幾個類型對象。
圖片
我們看到所有類型對象的類型都被設置成了 &PyType_Type,也就是 Python 里的 type。所以結論很清晰了,雖然內置的類型對象可以看做是 type 的實例對象,但它卻不是由 type 實例化得到的,而是在底層預定義好,并以全局變量的形式靜態(tài)出現。
所以內置的類型對象之間不存在誰創(chuàng)建誰,它們都是預定義好的,只是在定義的時候,將自身的類型設置成了 type 而已,包括 type 本身(類型還是 type)。這樣一來,每一個對象都會具有一個類型,從而將面向對象理念貫徹的更加徹底。
print(int.__class__)
print(tuple.__class__)
print(set.__class__)
print(type.__class__)
"""
<class 'type'>
<class 'type'>
<class 'type'>
<class 'type'>
"""
print(
type.__class__.__class__.__class__ is type
) # True
print(
type(type(type(type(type(type))))) is type
) # True
好,說完了這些之后我們來正式考察 type 和 object 的底層實現。
類型對象的類型:PyType_Type
type 是所有類型對象的類型,我們稱之為元類型或者元類,即 metaclass,當然它同時也是一個類型對象。下面看一下它的底層實現。
// Objects/typeobject.c
PyTypeObject PyType_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"type", /* tp_name */
sizeof(PyHeapTypeObject), /* tp_basicsize */
sizeof(PyMemberDef), /* tp_itemsize */
(destructor)type_dealloc, /* tp_dealloc */
offsetof(PyTypeObject, tp_vectorcall), /* tp_vectorcall_offset */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_as_async */
(reprfunc)type_repr, /* tp_repr */
&type_as_number, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
(ternaryfunc)type_call, /* tp_call */
// ...
};
所有的類型對象加上元類都是由 PyTypeObject 這個結構體實例化得到的,所以它們內部的字段都是一樣的。只不過傳入的值不同,實例化之后得到的結果也不同,可以是 PyLong_Type、可以是 PyFloat_Type,也可以是這里的 PyType_Type。
再看一下里面的宏 PyVarObject_HEAD_INIT,它用來初始化引用計數、類型和 ob_size,其中類型被初始化成了 &PyType_Type。換句話說,PyType_Type 里面的 ob_type 字段指向的還是 PyType_Type,而對應 Python 的話,就是 type 的類型還是 type。
>>> type.__class__
<class 'type'>
>>> type.__class__.__class__.__class__.__class__.__class__ is type
True
>>> type(type(type(type(type(type))))) is type
True
顯然不管套娃多少次,最終的結果都是True,這也是符合預期的。
類型對象的基類:PyBaseObject_Type
Python 中有兩個類型對象比較特殊,一個是站在類型金字塔頂端的 type,另一個是站在繼承金字塔頂端的 object。看完了 type,再來看看 object。
由于 object 的類型是 type,那么在初始化 PyBaseObject_Type 的時候,它的 ob_type 一定也被設置成了 &PyType_Type。
我們看一下 PyBaseObject_Type 的實現,它同樣定義在 Objects/typeobject.c 中。
圖片
類型對象在創(chuàng)建的時候,ob_type 字段都會被初始化成 &PyType_Type,而 object 也不例外,所以它的類型為 type,這個非常簡單。但 type 的基類是 object,又是怎么一回事呢?
之前介紹類型對象的時候,我們說類型對象內部的 tp_base 表示繼承的基類,那么對于 PyType_Type 來講,它內部的 tp_base 肯定是 &PyBaseObject_Type,即 object。
圖片
但令我們吃鯨的是,它的 tp_base 居然是個 0,如果為 0 的話則表示沒有這個屬性,或者說基類為空。不是說 type 的基類是 object 嗎?為啥 tp_base 是 0 呢。
事實上如果你去看其它類型的話,會發(fā)現它們內部的 tp_base 也是 0。為 0 的原因就在于我們目前看到的類型對象還不夠完善,因為 Python 的動態(tài)性,顯然不可能在定義的時候就將所有字段屬性都設置好、然后解釋器一啟動就得到我們平時使用的類型對象。
因此目前看到的類型對象還不是最終形態(tài),有一部分字段屬性是在解釋器啟動之后再動態(tài)完善的,而這個完善的過程被稱為類型對象的初始化,它由函數 PyType_Ready 負責。
圖片
首先代碼中的 type 只是一個普通的參數,當解釋器發(fā)現一個類對象還沒有初始化時,會將其作為參數傳遞給 PyType_Ready,進行初始化。
初始化過程會做很多的工作,用于完善類型對象,而其中一項工作就是設置基類。如果發(fā)現類型對象的基類為空,那么就將基類設置為 object,因為在 Python3 里面新式類都要繼承 object。當然啦,這個類不能是 object 本身,object 的基類是 None,因為繼承鏈向上要有一個終點。
當 PyType_Ready 完成初始化之后,就得到我們平常使用的類型對象了,最終 PyType_Type 和 PyBaseObject_Type 的關系如下。
圖片
因此到目前為止,type 和 object 之間的恩怨糾葛算是真相大白了,總結一下:
1)和自定義類不同,內置的類不是由 type 實例化得到的,它們都是在底層預先定義好的,不存在誰創(chuàng)建誰。只是內置的類在定義的時候,它們的類型都被設置成了 type。這樣不管是內置的類,還是自定義類,在調用時都會執(zhí)行 type 的 __call__ 函數,從而讓它們的行為是一致的。
2)雖然內置的類在底層預定義好了,但還有一些瑕疵,因為有一部分邏輯無法以源碼的形式體現,只能在解釋器啟動的時候再動態(tài)完善。而這個完善的過程,便包含了基類的填充,會將基類設置成 object。
所以 type 和 object 是同時出現的,它們的存在需要依賴彼此。首先這兩者會以不完全體的形式定義在源碼中,并且在定義的時候將 object 的類型設置成 type;然后當解釋器啟動的時候,再經過動態(tài)完善,進化成完全體,而進化的過程中會將 type 的基類設置成 object。
所以 object 的類型是 type,type 繼承 object 就是這么來的。
小結
至此,我們算是從解釋器的角度完全理清了 Python 中對象之間的關系,用之前的一張圖總結一下。
圖片
當然,目前還遠遠沒有結束,后續(xù)還會針對內置的對象進行專門的剖析,如浮點數、整數、字符串、字節(jié)串、元組、列表、字典、集合等等,都會一點一點剖析。我們會從 Python 的角度介紹對象該怎么用,然后再看它的底層實現,最后再用 Python 代碼進行驗證,加深理解。