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其它相關文章!

推薦閱讀: