Python源碼剖析 - 對象初探


01 前言

對象是 python 中最核心的一個概念,在python的世界中,一切都是對象,整數、字符串、甚至類型、整數類型、字符串類型,都是對象。

02 什么是PyObject

Python 中凡事皆對象,而其中 PyObject 又是所有對象的基礎,它是 Python 對象機制的核心。因為它是基類,而其他對象都是對它的繼承。

打開 Include/python.h 中聲明如下:

#define PyObject_HEAD                   \
    _PyObject_HEAD_EXTRA                \
    Py_ssize_t ob_refcnt;               \
    struct _typeobject *ob_type;

typedef struct _object {
    PyObject_HEAD
} PyObject;

PyObject 有兩個重要的成員對象:

  • ob_refcnt - 表示引用計數,當有一個新的 PyObject * 引用該對象時候,則進行 +1 操作;同時,當這個 PyObject * 被刪除時,該引用計數就會減小。當計數為0時,該對象就會被回收,等待內存被釋放。
  • ob_type 記錄對象的類型信息,這個結構體含有很多信息,見如下代碼分析。

03 類型對象

在python中,預先定義了一些類型對象,比如 int 類型、str 類型、dict 類型等,這些我們稱之為內建類型對象,這些類型對象實現了面向對象中"類"的概念。

這些內建對象實例化之后,可以創建類型對象所對應的實例對象,比如 int 對象、str 對象、dict 對象。這些實例對象可以視為面向對象理論中的“對象"這個概念在python中的體現。

#define PyObject_VAR_HEAD               \
    PyObject_HEAD                       \
    Py_ssize_t ob_size; /* Number of items in variable part */      
 
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;
    cmpfunc tp_compare;
    reprfunc tp_repr;

    /* Method suites for standard classes */

    PyNumberMethods *tp_as_number;
    PySequenceMethods *tp_as_sequence;
    PyMappingMethods *tp_as_mapping;
  
    /* Attribute descriptor and subclassing stuff */
    struct PyMethodDef *tp_methods;
    struct PyMemberDef *tp_members;
    struct PyGetSetDef *tp_getset;
    struct _typeobject *tp_base;
    PyObject *tp_dict;
    descrgetfunc tp_descr_get;
    descrsetfunc tp_descr_set;
    Py_ssize_t tp_dictoffset;
    initproc tp_init;
    allocfunc tp_alloc;
    newfunc tp_new;
    freefunc tp_free; /* Low-level free-memory routine */
    inquiry tp_is_gc; /* For PyObject_IS_GC */
    PyObject *tp_bases;
    PyObject *tp_mro; /* method resolution order */
    PyObject *tp_cache;
    PyObject *tp_subclasses;
    PyObject *tp_weaklist;
    destructor tp_del;

  
    ...
} PyTypeObject;

這當中,我們需要關注幾個重點成員變量:

  • tp_name 即類型名稱,例如 'int', tuple', 'list'等,可以標准輸出
  • tp_basicsize 與 tp_itemsize, 創建該對象的內存信息
  • 關聯操作
  • 描述該類型的其他信息

04 定長對象與變長對象

定長對象比較好理解,例如一個整數對象,無論這個數值多大,它的存儲長度是一定的,這個長度由 _typeobject 來指定,不會變化。

typedef struct {
    PyObject_HEAD
    long ob_ival;
} PyIntObject;

變長對象在內存中的長度是不一定的,所以需要 ob_size 來記錄變長部分的個數,需要注意的是,這個並不是字節的數目。

#define PyObject_VAR_HEAD               \
    PyObject_HEAD                       \
    Py_ssize_t ob_size; /* Number of items in variable part */

typedef struct {
    PyObject_VAR_HEAD;
    long ob_shash;
    int ob_sstate;
    char ob_sval[1];

    /* Invariants:
     *     ob_sval contains space for 'ob_size+1' elements.
     *     ob_sval[ob_size] == 0.
     *     ob_shash is the hash of the string or -1 if not computed yet.
     *     ob_sstate != 0 iff the string object is in stringobject.c's
     *       'interned' dictionary; in this case the two references
     *       from 'interned' to this object are *not counted* in ob_refcnt.
     */

} PyStringObject;

05 創建一個定長對象的例子

代碼如下:

a = int(10)

Python 主要做了以下操作:

  • 第一步:分析需要創建的類型,如上,則是 PyInt_Type
  • 第二步:根據 PyInt_Type 中的 int_new 函數來構造對象
  • 第三步:識別上述代碼中的 10 為字符傳,然后調用 PyInt_FromString() 函數來構造
  • 第四步:最后調用 PyInt_FromLong(long ival) 函數來進行整數對象的內存分配和賦值。

我們先看一下 PyInt_Type的代碼實現:

  • tp_name 被賦值為“int”,這樣在 type() 函數時,就會顯示該字符串
  • 指定 “int” 類的關聯操作,如釋放、打印、比較等
  • tp_basicsize 賦值為 sizeof(PyIntObject)
  • tp_itemsize 賦值為 0
PyTypeObject PyInt_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "int",
    sizeof(PyIntObject),
    0,
    (destructor)int_dealloc,                    /* tp_dealloc */
    (printfunc)int_print,                       /* tp_print */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    (cmpfunc)int_compare,                       /* tp_compare */
    (reprfunc)int_to_decimal_string,            /* tp_repr */
    &int_as_number,                             /* tp_as_number */
    0,                                          /* tp_as_sequence */
    0,                                          /* tp_as_mapping */
    (hashfunc)int_hash,                         /* tp_hash */
    0,                                          /* tp_call */
    (reprfunc)int_to_decimal_string,            /* tp_str */
    PyObject_GenericGetAttr,                    /* tp_getattro */
    0,                                          /* tp_setattro */
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES |
        Py_TPFLAGS_BASETYPE | Py_TPFLAGS_INT_SUBCLASS,          /* tp_flags */
    int_doc,                                    /* tp_doc */
    0,                                          /* tp_traverse */
    0,                                          /* tp_clear */
    0,                                          /* tp_richcompare */
    0,                                          /* tp_weaklistoffset */
    0,                                          /* tp_iter */
    0,                                          /* tp_iternext */
    int_methods,                                /* tp_methods */
    0,                                          /* tp_members */
    int_getset,                                 /* tp_getset */
    0,                                          /* tp_base */
    0,                                          /* tp_dict */
    0,                                          /* tp_descr_get */
    0,                                          /* tp_descr_set */
    0,                                          /* tp_dictoffset */
    0,                                          /* tp_init */
    0,                                          /* tp_alloc */
    int_new,                                    /* tp_new */
};

這里我們對 int_new 方法進行展開, int_new 方法就是創建函數,類似於 C++ 中的構造函數,用來生成PyIntObject 代碼如下:

static PyObject *
int_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    PyObject *x = NULL;
    int base = -909;
    static char *kwlist[] = {"x", "base", 0};

    if (type != &PyInt_Type)
        return int_subtype_new(type, args, kwds); /* Wimp out */
    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|Oi:int", kwlist,
                                     &x, &base))
        return NULL;
    if (x == NULL) {
        if (base != -909) {
            PyErr_SetString(PyExc_TypeError,
                            "int() missing string argument");
            return NULL;
        }
        return PyInt_FromLong(0L);
    }
    if (base == -909)
        return PyNumber_Int(x);
    if (PyString_Check(x)) {
        /* Since PyInt_FromString doesn't have a length parameter,
         * check here for possible NULs in the string. */
        char *string = PyString_AS_STRING(x);
        if (strlen(string) != PyString_Size(x)) {
            /* create a repr() of the input string,
             * just like PyInt_FromString does */
            PyObject *srepr;
            srepr = PyObject_Repr(x);
            if (srepr == NULL)
                return NULL;
            PyErr_Format(PyExc_ValueError,
                 "invalid literal for int() with base %d: %s",
                 base, PyString_AS_STRING(srepr));
            Py_DECREF(srepr);
            return NULL;
        }
        return PyInt_FromString(string, NULL, base);
    }
#ifdef Py_USING_UNICODE
    if (PyUnicode_Check(x))
        return PyInt_FromUnicode(PyUnicode_AS_UNICODE(x),
                                 PyUnicode_GET_SIZE(x),
                                 base);
#endif
    PyErr_SetString(PyExc_TypeError,
                    "int() can't convert non-string with explicit base");
    return NULL;
}

最后通過 PyInt_FromLong 方法對新產生的對象的type信息就行賦值為 PyInt_Type,並設置整數的具體數值。其中如果是小整數,則可以從 small_ints 數組中直接放回。

#define N_INTOBJECTS    ((BLOCK_SIZE - BHEAD_SIZE) / sizeof(PyIntObject))

#define BLOCK_SIZE      1000    /* 1K less typical malloc overhead */
#define BHEAD_SIZE      8       /* Enough for a 64-bit pointer */

static PyIntObject *small_ints[NSMALLNEGINTS + NSMALLPOSINTS];

struct _intblock {
    struct _intblock *next;
    PyIntObject objects[N_INTOBJECTS];
};

typedef struct _intblock PyIntBlock;

static PyIntBlock *block_list = NULL;
static PyIntObject *free_list = NULL;

PyObject *
PyInt_FromLong(long ival)
{
    register PyIntObject *v;
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
    if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) {
        v = small_ints[ival + NSMALLNEGINTS];
        Py_INCREF(v);
#ifdef COUNT_ALLOCS
        if (ival >= 0)
            quick_int_allocs++;
        else
            quick_neg_int_allocs++;
#endif
        return (PyObject *) v;
    }
#endif
    if (free_list == NULL) {
        if ((free_list = fill_free_list()) == NULL)
            return NULL;
    }
    /* Inline PyObject_New */
    v = free_list;
    free_list = (PyIntObject *)Py_TYPE(v);
    (void)PyObject_INIT(v, &PyInt_Type);
    v->ob_ival = ival;
    return (PyObject *) v;
}

06 展開

為了性能考慮,python 中對小整數有專門的緩存池,這樣就不需要每次使用小整數對象時去用 malloc 分配內存以及free釋放內存。

小整數之外的大整數怎么避免重復分配和回收內存呢?

Python 的方案是 PyIntBlock。PyIntBlock 這個結構就是一塊內存,里面保存 PyIntObject 對象。一個 PyIntBlock 默認存放 N_INTOBJECTS 對象。

PyIntBlock 鏈表通過 block_list 維護,每個block中都維護一個 PyIntObject 數組 objects,block 的 objects 可能會有些內存空閑,因此需要另外用一個 free_list 鏈表串起來這些空閑的項以方便再次使用。objects 數組中的 PyIntObject 對象通過 ob_type 字段從后往前鏈接。

小整數的緩存池最終實現也是生存在 block_list 維護的內存上,在 python 初始化時,會調用 PyInt_Init 函數申請內存並創建小整數對象。

更多內容

原文來自兔子先生網站:https://www.xtuz.net/detail-134.html

查看原文 >>> Python源碼剖析 - 對象初探

如果你對Python語言感興趣,可以關注我,或者關注我的微信公眾號:xtuz666


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM