Python對象的底層實現源碼學習
在“Python源碼學習筆記:Python萬物皆對象”中,我們對Python的對象類型體系有瞭一定的認識,這篇博客將從源碼層面來介紹Python中萬物皆對象的底層實現。
1. PyObject:對象的基石
在Python解釋器的C層面,一切對象都是以PyObject為基礎的
C源碼如下:
typedef struct _object { _PyObject_HEAD_EXTRA Py_ssize_t ob_refcnt; PyTypeObject *ob_type; } PyObject;
源碼解讀:
_PyObject_HEAD_EXTRA:主要用於實現雙向鏈表(分析源碼時暫時忽略)
ob_refcnt:引用計數,用於垃圾回收機制,當這個參數減少為0時即代表對象要被刪除瞭(Py_ssize_t當作int或long即可,感興趣的話可以去看下它的定義)
ob_type:類型指針,指向對象的類型對象(PyTypeObject,稍後介紹),類型對象描述實例對象的數據及行為。如PyLongObject的ob_type指向的就是PyLong_Type
2. PyVarObject:變長對象的基礎
PyVarObject與PyObject相比隻多瞭一個屬性ob_size,它指明瞭邊長對象中有多少個元素
C源碼如下:
typedef struct { PyObject ob_base; Py_ssize_t ob_size; /* Number of items in variable part */ } PyVarObject;
定長對象和變長對象的大致結構圖示如下:
宏定義:對於具體對象,視其大小是否固定,需要包含頭部PyObject或PyVarObject,為此,頭文件準備瞭兩個宏定義,方便其他對象使用:
#define PyObject_HEAD PyObject ob_base; #define PyObject_VAR_HEAD PyVarObject ob_base;
2.1 浮點對象
這裡簡單的以浮點對象作為定長對象的例子,介紹一下相關概念,後續會詳細分析float對象的源碼。
對於大小固定的浮點對象,需要在PyObject頭部的基礎上,用一個雙精度浮點數double加以實現:
typedef struct { PyObject_HEAD double ob_fval; } PyFloatObject;
圖示如下:
2.2 列表對象
這裡簡單的以列表對象作為變長對象的例子,介紹一下相關概念,後續會詳細分析list對象的源碼。
對於大小不固定的列表對象,需要在PyVarObject頭部的基礎上,用一個動態數組加以實現,數組存儲瞭列表包含的對象的指針,即PyObject指針:
typedef struct { PyObject_VAR_HEAD PyObject **ob_item; Py_ssize_t allocated; } PyListObject;
源碼解讀:
ob_item:指向動態數組的指針,數組中保存元素對象指針
allocated:動態數組的總長度,即列表當前的“容量”
ob_size:當前元素個數,即列表當前的長度(這裡的長度是指:列表包含n個元素,則長度為n)
圖示如下:
3. PyTypeObject:類型的基石
問題:不同類型的對象所需存儲空間不同,創建對象時從哪得知存儲信息呢?以及如何判斷一個給定對象支持哪些操作呢?
註意到,PyObject結構體中包含一個指針ob_type,指向的就是類型對象,其中就包含瞭上述問題所需要的信息
C源碼如下:(隻列出瞭部分,後續會結合具體類型進行分析)
typedef struct _typeobject { PyObject_VAR_HEAD const char *tp_name; /* For printing, in format "<module>.<name>" */ Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */ /* Methods to implement standard operations */ destructor tp_dealloc; printfunc tp_print getattrfunc tp_getattr; setattrfunc tp_setattr; // ... /* Attribute descriptor and subclassing stuff */ PyObject *tp_bases; // ... } PyTypeObject;
源碼解讀:
PyObject_VAR_HEAD表示PyTypeObject是變長對象
tp_name:類型名稱
tp_basicsize、tp_itemsize:創建實例對象時所需的內存信息
tp_print、tp_getattr等:表示該類型支持的相關操作信息
tp_bases:指向基類對象,表示類型的繼承信息
PyTypeObject就是類型對象在C層面的表示形式,對應面向對象中”類“的概念,其中保存著對象的”元信息“(即一類對象的操作、數據等)。
下面以浮點類型為例,列出瞭PyFloatObject和PyTypeObject之間的關系結構圖示:(其中兩個浮點實例對象都是PyFloatObject結構體,浮點類型對象float是一個PyTypeObject結構體變量)
由於浮點類型對象唯一,在C語言層面作為一個全局變量靜態定義即可。C源碼如下:(隻列出瞭部分)
PyTypeObject PyFloat_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "float", sizeof(PyFloatObject), 0, (destructor)float_dealloc, /* tp_dealloc */ // ... (reprfunc)float_repr, /* tp_repr */ // ... };
源碼解讀:
第二行PyVarObject_HEAD_INIT(&PyType_Type, 0):初始化瞭ob_refcnt、ob_type、ob_sie三個字段,其中ob_type指向瞭PyType_Type(稍後會繼續介紹,它就是type),即:float的類型是type
第三行"float":將tp_name字段初始化為類型名稱float
4. PyType_Type:類型的類型
通過PyFloat_Type的ob_type字段,我們找到瞭type所對應的C語言層面結構體變量:PyType_Type,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 */ // ... (reprfunc)type_repr, /* tp_repr */ // ... };
內建類型和自定義類對應的PyTypeObject對象都是通過這個PyType_Type創建的。在第二行PyVarObject_HEAD_INIT(&PyType_Type, 0)中,PyType_Type把自己的ob_type字段設置成瞭它自己,即type的類型是type
把PyType_Type加入到結構圖中,圖示如下:
5. PyBaseObject_Type:類型之基
object是另外一個特殊的類型,它是所有類型的基類。如果要找到object對應的結構體,我們可以通過PyFloat_Type的tp_base字段來尋找,因為它指向的就是float的基類object。但是我們查看源碼發現,PyFloat_Type中並沒有初始化tp_base字段:
同樣地,我們查看Objects文件夾下的各種不同類型所對應的結構體,發現tp_base字段均沒有初始化,於是尋找將tp_base字段初始化的函數:
void _Py_ReadyTypes(void) { if (PyType_Ready(&PyBaseObject_Type) < 0) Py_FatalError("Can't initialize object type"); if (PyType_Ready(&PyType_Type) < 0) Py_FatalError("Can't initialize type type"); // ... if (PyType_Ready(&PyFloat_Type) < 0) Py_FatalError("Can't initialize float type"); // ... }
_Py_ReadyTypes中統一調用瞭PyType_Ready()函數,為各種類型設置tp_base字段:
int PyType_Ready(PyTypeObject *type) { // ... /* Initialize tp_base (defaults to BaseObject unless that's us) */ base = type->tp_base; if (base == NULL && type != &PyBaseObject_Type) { base = type->tp_base = &PyBaseObject_Type; Py_INCREF(base); } // ... }
可以看到,PyType_Ready在初始化tp_base字段時,對於PyBaseObject_Type,不會設置tp_base字段,即object是沒有基類的,這就是為瞭保證繼承鏈有一個終點。
PyBaseObject_Type源碼如下:(隻列出瞭部分)
PyTypeObject PyBaseObject_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "object", /* tp_name */ sizeof(PyObject), /* tp_basicsize */ 0, /* tp_itemsize */ object_dealloc, /* tp_dealloc */ // ... object_repr, /* tp_repr */ // ... 0, /* tp_base */ // ... };
源碼解讀:
第二行PyVarObject_HEAD_INIT(&PyType_Type, 0):把ob_type設置為PyType_Type,即object的類型是type
將PyBaseObject_Type加入到結構圖中,圖示如下:
6. 補充
object的類型是type,type的基類是object。先有雞還是先有蛋?
答:
前面我們提到,在各種類型對應的C語言結構體變量初始化的時候,tp_base字段都是沒有設置具體值的,直到_Py_ReadyTypes()函數執行時,才通過PyType_Ready()去初始化各類型的tp_base。
在PyBaseObject_Type初始化時,會將ob_tyep字段設置為PyType_Type,即object的類型為type;在_Py_ReadyTypes函數中,會通過PyType_Ready()設置PyType_Type的tp_base字段為PyBaseObject_Type。所以這裡本質上不是一個先有雞還是先有蛋的問題。
PyTypeObject保存元信息:某種類型的實例對象所共有的信息保存在類型對象中,實例對象所特有的信息保存在實例對象中。以float為例:
- 無論是3.14,還是2.71,作為float對象,它們都支持加法運算,因此加法處理函數的指針就會保存在類型對象中,即float中。
- 而這兩個float對象的具體值都是各自特有的,因此具體數值會通過一個double類型的字段保存在實例對象中。
以上就是Python對象的底層實現源碼學習的詳細內容,更多關於Python對象底層的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- Python對象的生命周期源碼學習
- python源碼剖析之PyObject詳解
- python 中sys.getsizeof的用法說明
- 淺析Python的對象拷貝和內存佈局
- Python內建類型bytes深入理解