# Python List 实现原理

Python list 对象的一些介绍:https://www.runoob.com/python3/python3-list.html

基于 CPython3.9.12 版本阅读

在 CPython 中 list 对应 PyListObject,位于 objects/listobject.c

PyTypeObject PyList_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "list",
    sizeof(PyListObject),
    0,
    (destructor)list_dealloc,                   /* tp_dealloc */
    0,                                          /* tp_vectorcall_offset */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_as_async */
    (reprfunc)list_repr,                        /* tp_repr */
    0,                                          /* tp_as_number */
    &list_as_sequence,                          /* tp_as_sequence */
    &list_as_mapping,                           /* tp_as_mapping */
    PyObject_HashNotImplemented,                /* tp_hash */
    0,                                          /* tp_call */
    0,                                          /* tp_str */
    PyObject_GenericGetAttr,                    /* tp_getattro */
    0,                                          /* tp_setattro */
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
        Py_TPFLAGS_BASETYPE | Py_TPFLAGS_LIST_SUBCLASS, /* tp_flags */
    list___init____doc__,                       /* tp_doc */
    (traverseproc)list_traverse,                /* tp_traverse */
    (inquiry)_list_clear,                       /* tp_clear */
    list_richcompare,                           /* tp_richcompare */
    0,                                          /* tp_weaklistoffset */
    list_iter,                                  /* tp_iter */
    0,                                          /* tp_iternext */
    list_methods,                               /* tp_methods */
    0,                                          /* tp_members */
    0,                                          /* tp_getset */
    0,                                          /* tp_base */
    0,                                          /* tp_dict */
    0,                                          /* tp_descr_get */
    0,                                          /* tp_descr_set */
    0,                                          /* tp_dictoffset */
    (initproc)list___init__,                    /* tp_init */
    PyType_GenericAlloc,                        /* tp_alloc */
    PyType_GenericNew,                          /* tp_new */
    PyObject_GC_Del,                            /* tp_free */
    .tp_vectorcall = list_vectorcall,
};

分别定义了__new__方法和__init__,先来看看在调用 list () 时,做了什么,__new__调用了 PyType_GenericNew

PyObject *
PyType_GenericNew(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    return type->tp_alloc(type, 0); // 先为对象分配空间
}

__init__调用了 list___init__方法

static int
list___init__(PyObject *self, PyObject *args, PyObject *kwargs)
{
    int return_value = -1; // 返回值,-1 在 CPython 里表示异常,(hash 不会出现 - 1, 为 - 1 的哈希会返回 - 2)
    PyObject *iterable = NULL;
    if (Py_IS_TYPE(self, &PyList_Type) &&
        !_PyArg_NoKeywords("list", kwargs)) {
	// 如果类型为 List 并且没有关键字,则直接结束
        goto exit;
    }
    if (!_PyArg_CheckPositional("list", PyTuple_GET_SIZE(args), 0, 1)) {
	// 如果传递的位置参数不为 0 到 1 个,
        goto exit;
    }
    if (PyTuple_GET_SIZE(args) < 1) {
	// 如果位置参数参数小于 1,则跳到 skip_optional 创建 list, 但是是空列表
        goto skip_optional;
    }
    iterable = PyTuple_GET_ITEM(args, 0); // 获取 args 的第一个元素,也就是 [...]
skip_optional:
    return_value = list___init___impl((PyListObject *)self, iterable); // 创建列表
exit:
    return return_value;
}
static int
list___init___impl(PyListObject *self, PyObject *iterable)
/*[clinic end generated code: output=0f3c21379d01de48 input=b3f3fe7206af8f6b]*/
{
    /* Verify list invariants established by PyType_GenericAlloc() */
    assert(0 <= Py_SIZE(self));
    assert(Py_SIZE(self) <= self->allocated || self->allocated == -1);
    assert(self->ob_item != NULL ||
           self->allocated == 0 || self->allocated == -1);
    /* Empty previous contents */
    if (self->ob_item != NULL) {
        (void)_list_clear(self);
    }
    if (iterable != NULL) { // 如果传递了 [...] 对象,则进入 if statement
        if (_PyObject_HasLen(iterable)) {
	    /*
		_PyObject_HasLen 主要判断 iterable 对象是否实现了序列协议的对象相关字段附加结构 (tp_as_sequence) 或者
		是否实现了映射协议的对象相关字段的附加结构 (tp_as_mapping)
	    */
            Py_ssize_t iter_len = PyObject_Size(iterable);
	    /*
		PyObject_Size 主要用来获取 iterable 对象的长度,如果该对象定义了 tp_as_sequence 协议,
		则会判断 tp_as_sequence 协议是否实现__len__方法,定义了则调用__len__, 如果没有定义,
		则再次获取 tp_as_mapping 协议,并在 tp_as_mapping 协议里判断是否定义__len__,定义了则调用,如果两个协议都没有
		定义__len__, 则返回 - 1
	    */
            if (iter_len == -1) {
                if (!PyErr_ExceptionMatches(PyExc_TypeError)) {
		    // 创建失败
                    return -1;
                }
                PyErr_Clear();
            }
            if (iter_len > 0 && self->ob_item == NULL
                && list_preallocate_exact(self, iter_len)) {
		// 长度大于 0、ob_item 为 NULL, 用于真正存储列表的元素,list_preallocate_exact 为列表预分配内存,调用 C 的 malloc
                return -1;
            }
        }
        PyObject *rv = list_extend(self, iterable); // 最主要的方法,代码太长,分开粘
        if (rv == NULL)
            return -1;
        Py_DECREF(rv);
    }
    return 0;
}

经过源码分析发现,在调用 list ([…]) 时,实际上是调用了 list 对象的 extend 方法,代码也比较长,一点一点分析😵

static PyObject *
list_extend(PyListObject *self, PyObject *iterable)
/*[clinic end generated code: output=630fb3bca0c8e789 input=9ec5ba3a81be3a4d]*/
{
    PyObject *it;      /* iter(v) */
    Py_ssize_t m;                  /* size of self */
    Py_ssize_t n;                  /* guess for size of iterable */
    Py_ssize_t mn;                 /* m + n */
    Py_ssize_t i;
    PyObject *(*iternext)(PyObject *);
    /* Special cases:
       1) lists and tuples which can use PySequence_Fast ops
       2) extending self to self requires making a copy first
    */
    if (PyList_CheckExact(iterable) || PyTuple_CheckExact(iterable) ||
                (PyObject *)self == iterable) { // 判断 iterable 是否为 list 类型或者 tuple 类型
        PyObject **src, **dest; // 两个二级指针
        iterable = PySequence_Fast(iterable, "argument must be iterable");
        if (!iterable)
            return NULL;
        n = PySequence_Fast_GET_SIZE(iterable); // 获取 iterable 的大小
        if (n == 0) {
            /* short circuit when iterable is empty */
            Py_DECREF(iterable);
            Py_RETURN_NONE;
        }
        m = Py_SIZE(self); // 获取自身的大小
        /* It should not be possible to allocate a list large enough to cause
        an overflow on any relevant platform */
        assert(m < PY_SSIZE_T_MAX - n);
        if (list_resize(self, m + n) < 0) { // 扩容操作,比较重要,后面会讲
            Py_DECREF(iterable);
            return NULL;
        }
        /* note that we may still have self == iterable here for the
         * situation a.extend(a), but the following code works
         * in that case too.  Just make sure to resize self
         * before calling PySequence_Fast_ITEMS.
         */
        /* populate the end of self with iterable's items */
        src = PySequence_Fast_ITEMS(iterable); // 获取 iterable 的 ob_item, 真正用于存储元素
        dest = self->ob_item + m;
        for (i = 0; i < n; i++) { // 循环操作,将 iterable 里的每个元素设置到 self->ob_item 里
            PyObject *o = src[i];
            Py_INCREF(o);
            dest[i] = o;
        }
        Py_DECREF(iterable); // 引用计数 - 1
        Py_RETURN_NONE; // 返回 None, 结束
    }
    // 如果 iterable 不是 list 或者 tuple 类型,则会走到这
    it = PyObject_GetIter(iterable); // 获取 iterable 的__iter__方法、调用,获取 iterator
    if (it == NULL)
	// 如果__iter__返回空则调用结束,失败
        return NULL;
    iternext = *Py_TYPE(it)->tp_iternext; // 从 iterator 中获取__next__方法
    /* Guess a result list size. */
    n = PyObject_LengthHint(iterable, 8); // 猜测结果列表的大小
    if (n < 0) {
	// 如果小于 0, 失败,结束调用
        Py_DECREF(it);
        return NULL;
    }
    m = Py_SIZE(self); // 获取自身的大小
    if (m > PY_SSIZE_T_MAX - n) { // 表示溢出
        /* m + n overflowed; on the chance that n lied, and there really
         * is enough room, ignore it.  If n was telling the truth, we'll
         * eventually run out of memory during the loop.
         */
    }
    else {
        mn = m + n; // 自身大小加上 iterator 的大小
        /* Make room. */
        if (list_resize(self, mn) < 0) // 判断是否需要扩容
            goto error;
        /* Make the list sane again. */
        Py_SET_SIZE(self, m); // 继续保持大小
    }
    /* Run iterator to exhaustion. */
    for (;;) {
	// 死循环,不断调用__next__, 直到消耗完
        PyObject *item = iternext(it);
        if (item == NULL) {
	    //__next__返回空,结束循环
            if (PyErr_Occurred()) {
                if (PyErr_ExceptionMatches(PyExc_StopIteration))
                    PyErr_Clear();
                else
                    goto error;
            }
            break;
        }
        if (Py_SIZE(self) < self->allocated) { // 如果自身大小小于已分配大小,将元素设置到最后的位置,并重新设置大小
            /* steals ref */
            PyList_SET_ITEM(self, Py_SIZE(self), item);
            Py_SET_SIZE(self, Py_SIZE(self) + 1); // 自身大小 ob_size + 1
        }
        else {
	    // 没有大于已分配大小
            int status = app1(self, item); // 调用 app1 设置元素
            Py_DECREF(item);  /* append creates a new ref */
            if (status < 0)
		// 小于 0 可能因为内存溢出或者扩容失败
                goto error;
        }
    }
    /* Cut back result list if initial guess was too large. */
    if (Py_SIZE(self) < self->allocated) {
	// 这里也比较重要,再次判断自身大小是否小于已分配大小,并进行缩容,不需要多余的内存
        if (list_resize(self, Py_SIZE(self)) < 0)
            goto error;
    }
    Py_DECREF(it);
    Py_RETURN_NONE;
  error:
    Py_DECREF(it);
    return NULL;
}
static int
app1(PyListObject *self, PyObject *v)
{
    Py_ssize_t n = PyList_GET_SIZE(self); // 获取 list 对象大小
    assert (v != NULL);
    if (n == PY_SSIZE_T_MAX) {
        PyErr_SetString(PyExc_OverflowError,
            "cannot add more objects to list");
        return -1;
    }
    // 扩容操作,self 是列表,n+1 就是 newsize, 或者说新的 ob_size
    // 里面有很重要的一步,就是将列表的 ob_size 设置为 newsize, 也就是 n+1
    // 因为 append 之后列表长度大小会变化,而 ob_size 则时刻维护着这个大小
    if (list_resize(self, n+1) < 0)
	// 是否需要扩容
        return -1;
    Py_INCREF(v);
    PyList_SET_ITEM(self, n, v); // 根据大小 n 作为下标设置元素的位置
    return 0;
}
PyObject *
PySequence_Fast(PyObject *v, const char *m)
{
    //v 为传递进来的 iterable 对象
    PyObject *it;
    if (v == NULL) {
	// 为空则抛异常
        return null_error();
    }
    if (PyList_CheckExact(v) || PyTuple_CheckExact(v)) {
	// 校验是否为 list 或者为 typle, 如果满足条件,则直接返回该对象,否则往下走,创建一个空 list, 再调用 list_extend
        Py_INCREF(v);
        return v;
    }
    it = PyObject_GetIter(v); // 从 iterable 对象中获取__iter__方法,调用返回 iterator
    if (it == NULL) {
	// 如果返回了空,抛异常
        if (PyErr_ExceptionMatches(PyExc_TypeError))
            PyErr_SetString(PyExc_TypeError, m);
        return NULL;
    }
    v = PySequence_List(it); // 创建一个 list 对象
    Py_DECREF(it);
    return v;
}
PyObject *
PySequence_List(PyObject *v)
{
    PyObject *result;  /* result list */
    PyObject *rv;          /* return value from PyList_Extend */
    if (v == NULL) {
        return null_error();
    }
    result = PyList_New(0); // 创建 list,大小为 0
    if (result == NULL)
        return NULL;
    rv = _PyList_Extend((PyListObject *)result, v); // 里面调用的就是 extend 方法
    if (rv == NULL) {
        Py_DECREF(result);
        return NULL;
    }
    Py_DECREF(rv);
    return result;
}
PyObject *
_PyList_Extend(PyListObject *self, PyObject *iterable)
{
    return list_extend(self, iterable);
}

list_extend 做的事情其实并不复杂,主要先判断你传递的参数是否为 list 对象或者 tuple 对象,如果满足,则将该对象里的每个元素通过 for 循环一个一个设置到列表里,如果不是判断不是 list、tuple 对象,就会尝试获取该对象的__iter__方法,并 new 一个空列表,再通过调用 list_extend 重新走一遍;如果你一开始传递的参数就不是 list 或者 tuple,它就会直接获取该对象的__iter__方法,调用获取 iterator,然后获取 iterator 的__next__方法,并通过 for 死循环不断调用__next__获取元素并设置到列表,中间会一直判断是否需要扩容操作,最后还会判断是否需要进行缩容操作

# 列表扩容操作

创建 list 时,app1 方法在每次设置元素时都会调用 list_resize 判断是否需要扩容,现在来看看具体实现

static int
list_resize(PyListObject *self, Py_ssize_t newsize)
{
    //self 就是列表,newsize 就是指元素在添加之后的 ob_size
    // 比如列表当前 ob_size 为 5,添加一个元素之后为 6,发现容量不够,就会扩容,那么 newsize 就为 6
    PyObject **items; // 二级指针,用来指向指针数组的
    size_t new_allocated, num_allocated_bytes; // 新的容量、对应的内存大小
    Py_ssize_t allocated = self->allocated; // 获取原来的容量
    /* Bypass realloc() when a previous overallocation is large enough
       to accommodate the newsize.  If the newsize falls lower than half
       the allocated size, then proceed with the realloc() to shrink the list.
    */
    // 如果 newsize 达到容量的一般,但还没有超过容量,那么意味着 newsize、或者新的 ob_size 和容量是匹配的,不需要扩容
    if (allocated >= newsize && newsize >= (allocated >> 1)) {
        assert(self->ob_item != NULL || newsize == 0);
        Py_SET_SIZE(self, newsize); // 只需要将列表的 ob_size 设置为 newsize
        return 0;
    }
    /* This over-allocates proportional to the list size, making room
     * for additional growth.  The over-allocation is mild, but is
     * enough to give linear-time amortized behavior over a long
     * sequence of appends() in the presence of a poorly-performing
     * system realloc().
     * Add padding to make the allocated size multiple of 4.
     * The growth pattern is:  0, 4, 8, 16, 24, 32, 40, 52, 64, 76, ...
     * Note: new_allocated won't overflow because the largest possible value
     *       is PY_SSIZE_T_MAX * (9 / 8) + 6 which always fits in a size_t.
     */
    // 走到这,说明容量与 ob_size 不匹配了,需要进行扩容或者缩容
    // 需要申请新的底层数组,具体申请多少个,这个是公式
    new_allocated = ((size_t)newsize + (newsize >> 3) + 6) & ~(size_t)3;
    /* Do not overallocate if the new size is closer to overalocated size
     * than to the old size.
     */
    if (newsize - Py_SIZE(self) > (Py_ssize_t)(new_allocated - newsize))
        new_allocated = ((size_t)newsize + 3) & ~(size_t)3;
    if (newsize == 0) // 如果 newsize 为 0,那么容量也会变成 0,假设将列表全部清空了,容量就会变为 0
        new_allocated = 0;
    num_allocated_bytes = new_allocated * sizeof(PyObject *); // 在 list 数组中,存放的都是 PyObject *,所以要计算内存
    // 调用 C malloc 申请相应大小的内存,将其指针交给 items
    items = (PyObject **)PyMem_Realloc(self->ob_item, num_allocated_bytes);
    if (items == NULL) {
	// 申请内存失败,没有内存了
        PyErr_NoMemory();
        return -1;
    }
    self->ob_item = items; // 指向新的数组,此时列表就发生了扩容或缩容
    Py_SET_SIZE(self, newsize); // 设置列表的 ob_size 为 newsize
    self->allocated = new_allocated; // 设置新的容量大小
    return 0;
}

The growth pattern is: 0, 4, 8, 16, 24, 32, 40, 52, 64, 76, …,当发现容量不够时才会开始扩容

newsize = 0
ob_size = 0
allocated = 0
for i in range(100):
    newsize += 1
    if (allocated >= newsize and newsize >= (allocated >> 1)):
        ob_size = newsize
        continue
    new_allocated = (newsize + (newsize >> 3) + 6) & ~3
    print("new allocated: ", new_allocated)
    if (newsize - ob_size > (new_allocated - newsize)):
        new_allocated = (newsize + 3) & ~3
        print("update new allocated: ", new_allocated)
    ob_size = newsize
    allocated = new_allocated
=====================> 打印结果
new allocated:  4
new allocated:  8
new allocated:  16
new allocated:  24
new allocated:  32
new allocated:  40
new allocated:  52
new allocated:  64
new allocated:  76
new allocated:  92
new allocated:  108

不断往空列表中 append 元素时,容量就会按上面的规律变化

lst = []
allocated = 0
for item in range(100):
    lst.append(item)
    # 计算 ob_size
    ob_size = len(lst)
    if ob_size > allocated:
        allocated = (lst.__sizeof__() - [].__sizeof__()) // 8
        print("new allocated: ", allocated, "ob size: ", lst.__sizeof__() - [].__sizeof__())
=====================> 打印结果
new allocated:  4 ob size:  32
new allocated:  8 ob size:  64
new allocated:  16 ob size:  128
new allocated:  24 ob size:  192
new allocated:  32 ob size:  256
new allocated:  40 ob size:  320
new allocated:  52 ob size:  416
new allocated:  64 ob size:  512
new allocated:  76 ob size:  608
new allocated:  92 ob size:  736
new allocated:  108 ob size:  864

如果直接通过 lst = [] 这种方式创建列表,那么其长度和容量是一样的

lst = [0] * 1000
# 长度与容量是一样的
print(len(lst), (lst.__sizeof__() - [].__sizeof__()) // 8)
# 如果此时添加一个元素,那么 ob_size 就会变为 1001, 大于容量 1000
# 此时列表会进行扩容,按照计算公式进行变化
# new_allocated = ((size_t)newsize + (newsize >> 3) + 6) & ~(size_t)3;
print("新容量: ", (1001 + (1001 >> 3) + 6) & ~3) # 新容量: 1132
# 添加一个元素
lst.append(0)
# 计算容量,结果是一样的
print((lst.__sizeof__() - [].__sizeof__()) // 8) # 1132

了解完扩容,还有一个缩容的情况,假如在扩容操作时,发现长度没有超过容量但又达到容量的一半,是不会缩容的,如果发现空闲了 8 个位置,但只需要两个,那么此时就会进行缩容操作

lst = [0] * 1000
# 长度与容量是一样的
print(len(lst), (lst.__sizeof__() - [].__sizeof__()) // 8) # 1000:1000
# 删除 500 个元素,此时 ob_size 为 500
lst[:500] = []
# 此时容量达到容量一半,不会缩容
print(len(lst), (lst.__sizeof__() - [].__sizeof__()) // 8) # 1000:1000
# 如果再删除一个元素,那么此时就会进行缩容操作 ob_size 变成 499, 小于 1000 // 2
# 缩容公式还是一样的: new_allocated = ((size_t) newsize + (newsize >> 3) + 6) & ~(size_t) 3;
print((499 + (499 >> 3) + 6) & ~3) # 564
_ = lst.pop()
print(len(lst), (lst.__sizeof__() - [].__sizeof__()) // 8) # 长度 499 容量 564

此外还有一个判断为 if (newsize == 0),如果 newsize 为 0,那么容量也是 0

lst = [0] * 1000
print(len(lst), (lst.__sizeof__() - [].__sizeof__()) // 8) # 1000:1000
lst[:] = []
print(len(lst), (lst.__sizeof__() - [].__sizeof__()) // 8) # 0:0
print((0 + (0 >> 3) + 6) & ~3) # 4

当发现 newsize 为 0 时,就会把容量设置为 0,CPython 认为列表长度为 0 的话,可能你并不想使用这个列表

# 获取元素

我们在使用列表时,可以通过下标:lst [1] 这种方式获取元素,那么底层是怎么实现的?

PyListType 里定义了映射协议结构

static PyMappingMethods list_as_mapping = {
    (lenfunc)list_length,
    (binaryfunc)list_subscript,
    (objobjargproc)list_ass_subscript
};

通过下标获取元素时会触发 list_subscript 方法

static PyObject *
list_subscript(PyListObject* self, PyObject* item)
{
    // 先判断 item 是不是一个整数,item 除了整数之外,也可以是切片
    if (_PyIndex_Check(item)) {
        Py_ssize_t i;
	// 检测 i 是否合法,因为 Python 的整型没有限制,列表长度和容量是由具体类型的变量维护的,这个数是有范围的
	// 如果输入一个过大的数字,Python 会报错:IndexError: cannot fit 'int' into an index-sized integer
        i = PyNumber_AsSsize_t(item, PyExc_IndexError);
        if (i == -1 && PyErr_Occurred()) // 设置异常
            return NULL;
        if (i < 0)
	    // 说明索引是负数,那么加上列表长度就变为正数索引了
            i += PyList_GET_SIZE(self);
        return list_item(self, i); // 调用 list_item 通过 ob_item [i] 获取元素,方法在下边
    }
    else if (PySlice_Check(item)) {
	// 走到这说明是一个切片
	//start: 切片起始位置、stop: 切片结束位置、step:步长
	//slicelength: 获取元素个数,比如 [1:5:2], 那么就为 2, 因为只能获取索引为 1 和 3 的元素
	//i: 循环变量,因为切片只能循环获取每一个元素,比如 [1:5:2], 需要循环两次,第一次 cur 为 1, 第二次 cur 为 3
        Py_ssize_t start, stop, step, slicelength, i;
        size_t cur; // 底层数组中元素的索引
        PyObject* result; // 返回结果
        PyObject* it;
        PyObject **src, **dest;
        if (PySlice_Unpack(item, &start, &stop, &step) < 0) {
	    // 对切片 item 进行解包,得到起始、结束、步长
            return NULL;
        }
	// 计算 slicelength, 我记得前面的文章好像分析过 PySlice_AdjustIndices 里面的源码,是不是 range 源码解析❓
        slicelength = PySlice_AdjustIndices(Py_SIZE(self), &start, &stop,
                                            step);
	// 如果 slicelength 为 0, 说明没有元素可以获取,因此直接返回一个空列表即可
        if (slicelength <= 0) {
            return PyList_New(0);
        }
	// 如果步长为 1, 那么在 list_slice 内部会创建一个 PyListObject *np, 申请相应的底层数组,设置 allocated
	//allocated 为 stop-start, 然后将参数列表中索引为 ilow 的元素到索引为 ihigh 的元素依次拷贝到 np->ob_item, 然后设置 ob_size 并返回
        else if (step == 1) {
            return list_slice(self, start, stop);
        }
        else {
	    // 走到这,说明步长不为 1, result 为 PyListObject *, 底层数组没有存储在 PyListObject 中,而是通过 ob_item 关联
	    //list_new_prealloc 就是申请底层数组、设置容量,这里容量为 slicelength
            result = list_new_prealloc(slicelength);
            if (!result) return NULL;
            src = self->ob_item; //src 为二级指针
            dest = ((PyListObject *)result)->ob_item; // 同理 src
	    // 进行循环操作,cur 从 start 开始遍历,每次加上 step 步长
            for (cur = start, i = 0; i < slicelength;
                 cur += (size_t)step, i++) {
                it = src[cur]; //it 就是 self->ob_item 中的元素
                Py_INCREF(it); // 获取元素的对象引用计数 + 1
                dest[i] = it; // 将获取元素设置到 dest
            }
            Py_SET_SIZE(result, slicelength); // 将大小设置为 slicelength, 说明通过切片创建新列表,其长度和容量也是一致的
            return result; // 返回结果
        }
    }
    else {
	//item 不合法
        PyErr_Format(PyExc_TypeError,
                     "list indices must be integers or slices, not %.200s",
                     Py_TYPE(item)->tp_name);
        return NULL;
    }
}
static PyObject *
list_item(PyListObject *a, Py_ssize_t i)
{
    // 检测索引 i 的合法性,如果 i > 列表长度,那么会报索引越界错误
    if (!valid_index(i, Py_SIZE(a))) {
	// 如果索引为负数也会报索引越界错误,因为上面已经对负数做了处理了,如果还是一个负数,那么同样报错
	// 假设列表长度为 3, 索引为 - 100, 加上长度后是 - 97, 结果还是负数,所以还是会报错
        if (indexerr == NULL) {
            indexerr = PyUnicode_FromString(
                "list index out of range");
            if (indexerr == NULL)
                return NULL;
        }
        PyErr_SetObject(PyExc_IndexError, indexerr);
        return NULL;
    }
    Py_INCREF(a->ob_item[i]); // 引用计数 + 1
    return a->ob_item[i]; // 通过下标获取第 i 个元素
}

分析过后,其实列表的切片操作也不是什么神奇的操作,实现的逻辑也比较简单

# 创建 PyListObject

创建一个列表,在 Python 底层只提供了唯一一个 Python/C API,此方法接收一个 size 参数,允许我们在创建一个 PyListObject 时,指定底层数组的长度

PyObject *
PyList_New(Py_ssize_t size)
{
    PyListObject *op; // 声明一个 PyListObject * 对象
    if (size < 0) {
	//size 大小小于 0, 抛异常
        PyErr_BadInternalCall();
        return NULL;
    }
    // 缓存池是否可用
    if (numfree) {
	// 将缓存池内对象个数 - 1
        numfree--;
	// 从缓存池中获取一个
        op = free_list[numfree];
	// 设置引用计数
        _Py_NewReference((PyObject *)op);
    } else {
	// 缓存池不可用时,就申请内存
        op = PyObject_GC_New(PyListObject, &PyList_Type);
        if (op == NULL) // 申请失败
            return NULL;
    }
    if (size <= 0)
	// 如果 size 小于等于 0, ob_item 就设置为 NULL
        op->ob_item = NULL;
    else {
	// 否则,创建一个指定容量的指针数组,然后让 ob_item 指向它
	// 所以是先创建 PyListObject 对象,然后创建底层数组,最后通过 ob_item 关联
        op->ob_item = (PyObject **) PyMem_Calloc(size, sizeof(PyObject *));
        if (op->ob_item == NULL) {
            Py_DECREF(op);
            return PyErr_NoMemory();
        }
    }
    Py_SET_SIZE(op, size); // 设置 ob_size
    op->allocated = size; // 设置 allocated
    _PyObject_GC_TRACK(op); // GC 管理
    return (PyObject *) op;
}

在创建 PyListObject 对象时,会先检测缓存池是否可用,有就直接拿来用,否则通过 malloc 在系统堆上申请,缓存池最多维护 80 个 PyListObject 对象

/* Empty list reuse scheme to save calls to malloc and free */
#ifndef PyList_MAXFREELIST
#  define PyList_MAXFREELIST 80
#endif
static PyListObject *free_list[PyList_MAXFREELIST];

既然能从缓存池中获取,那么在执行析构函数时,也要把列表放到缓存池中

static void
list_dealloc(PyListObject *op)
{
    Py_ssize_t i;
    PyObject_GC_UnTrack(op);
    Py_TRASHCAN_BEGIN(op, list_dealloc)
    if (op->ob_item != NULL) { // 底层数组不为空时
        /* Do it backwards, for Christian Tismer.
           There's a simple test case where somehow this reduces
           thrashing when a *very* large list is created and
           immediately deleted. */
        i = Py_SIZE(op);
        while (--i >= 0) {
	    // 将底层数组中每个指针指向的对象的引用计数都 - 1
            Py_XDECREF(op->ob_item[i]);
        }
        PyMem_FREE(op->ob_item); // 释放底层数组所占的内存
    }
    // 判断缓存池里面 PyLitObject 对象的个数,如果没满,就添加到缓存池
    if (numfree < PyList_MAXFREELIST && PyList_CheckExact(op))
        free_list[numfree++] = op;
    else
	// 否则缓存池满了,就将 PyListObject 对象所占的内存释放
        Py_TYPE(op)->tp_free((PyObject *)op);
    Py_TRASHCAN_END
}

在创建一个 PyListObject 对象时,实际上分为了两步,先创建 PyListObject 对象,然后创建底层数组,最后让 PyListObject 对象中的 ob_item 指向这个底层数组,那么销毁一个 PyListObject 对象时,也就先销毁 ob_item 维护的底层数组,然后再释放 PyListObject 对象本身(在缓存池已满的情况下)

16594995161.jpg

可以看到,b 对象与 a 对象的地址是一致的

还有一些方法,有需要可以自己去查看源码了解一下,我懒得看了😂

static PyMethodDef list_methods[] = {
    {"__getitem__", (PyCFunction)list_subscript, METH_O|METH_COEXIST, "x.__getitem__(y) <==> x[y]"},
    LIST___REVERSED___METHODDEF
    LIST___SIZEOF___METHODDEF
    LIST_CLEAR_METHODDEF
    LIST_COPY_METHODDEF
    LIST_APPEND_METHODDEF
    LIST_INSERT_METHODDEF
    LIST_EXTEND_METHODDEF
    LIST_POP_METHODDEF
    LIST_REMOVE_METHODDEF
    LIST_INDEX_METHODDEF
    LIST_COUNT_METHODDEF
    LIST_REVERSE_METHODDEF
    LIST_SORT_METHODDEF
    {"__class_getitem__", (PyCFunction)Py_GenericAlias, METH_O|METH_CLASS, PyDoc_STR("See PEP 585")},
    {NULL,              NULL}           /* sentinel */
};
Edited on Views times

Give me a cup of [coffee]~( ̄▽ ̄)~*

小芳芳 WeChat Pay

WeChat Pay

小芳芳 Alipay

Alipay