python 3.8.2 / 内置的数据结构 / list (类似于 STL 中的 vector)

一、特点

(1)相对于 tuple 来说,list 是动态的(mutable),即:各个元素都是可变的。

(2)可以通过索引进行查询。

(3)list 中的元素可以是 python 中的任何对象。例如:list、tuple、dict、set、字符串和整数,并且可以任意混合。

(4)所有元素由一个中括号“[ ]”包裹。

二、相关操作

1、增

        a、append() ,在 list 尾部插入元素。

        b、insert(),在 list 的指定位置插入元素。

2、删

        a、pop(),弹出 list 尾部元素并返回,与 append 对应。

        b、remove(),删除指定元素。

        c、del ,删除指定的位置的元素。

3、改

        直接用“=”修改指定元素即可。

4、查

        类似于数组的索引。

栗子:

if __name__ == '__main__':
    testlist = ['one', 'two', 'six']

    # 增
    # append 在 list 末尾添加元素。
    testlist.append('three')

    # insert 在指定的位置插入元素。
    testlist.insert(1, 'four')

    # 删
    # pop 删除 list 尾部元素。
    t = testlist.pop()

    # remove 删除 list 中的元素。
    testlist.remove('two')

    # del 删除 list 中指定范围的元素。
    del testlist[0:2]

    # 改
    testlist[0] = 'hello world!'

三、实现原理

1、底层的实现方式是 PyObject 类型的二维指针数组 。在 python 世界中,一切都是对象,无论是 int 、string 还是 list 等,这些都继承于 PyObject ,所以 list 保存的是各个 PyObject 的指针,传指针相对于传对象效率更高。

2、空 list 的大小是 40B,即:一个描述 list 的结构体的大小。该结构体如下所示:

typedef struct {
    PyObject_VAR_HEAD      // 用来保存已使用的内存槽的数量。
    PyObject **ob_item;    // 用来保存对象的指针的指针数组。
    Py_ssize_t allocated;  // 预先分配的内存槽的总容量,即:ob_item 数组的容量。
} PyListObject;

3、当一个空的 list 执行 append 时,list 实际上会多分配一些内存,这样可以提高下次 append 的高效性,即:时间复杂度为 O(1)。分配内存的算法如下:

new_allocated = (size_t)newsize + (newsize >> 3) + (newsize < 9 ? 3 : 6);

 总体效果是随着对象的数量增多,单次分配的内存槽逐渐变大。

4、append() 源码实现

(1)过程简述

  • 判断是否需要重新对 list 的内存槽数组(ob_item)进行重新分配。若需要,则申请新的内存槽数组、将旧内存槽数组中的数据拷贝到新的内存槽数组中,释放旧的内存槽数组。
  • 将新对象的指针加入到 ob_item 的尾部。

(2)append() 函数实际上调用的是 app1() 函数。

/**
 * 向 list 的尾部添加对象。
*/
static PyObject *list_append(PyListObject *self, PyObject *object)
{
    if (app1(self, object) == 0)
        Py_RETURN_NONE;
    return NULL;
}

(3)app1 函数的执行过程如下:

static int app1(PyListObject *self, PyObject *v)
{
    /**
     * 返回当前 list 中对象的数量。
    */
    Py_ssize_t n = PyList_GET_SIZE(self);

    assert(v != NULL);
    if (n == PY_SSIZE_T_MAX)
    {
        PyErr_SetString(PyExc_OverflowError,
                        "cannot add more objects to list");
        return -1;
    }
    /**
     * 重新调整 list 的内存。
     * 创建新的内存槽数组、释放旧的内存槽数组。
    */
    if (list_resize(self, n + 1) < 0)
        return -1;
    /**
     * 对象 v 的引用计数 + 1 。
    */
    Py_INCREF(v);

    /**
     * 将对象指针插入到 list 的 ob_item 指针数据中。
    */
    PyList_SET_ITEM(self, n, v);
    return 0;
}

(4)精髓就在 list_resize() 函数中了,该函数完成了内存的重新分配。

static int list_resize(PyListObject *self, Py_ssize_t newsize)
{
    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.
    */
    /**
     * (1)若 newsize <  已分配的内存槽的数量的 1/2,则重新分配内存槽数组(缩容)。
     * (2)若 newsize >= 已分配的内存槽的数量的 1/2,且 newsize =< 已分配的内存槽的数量,则无需调整。
     * (3)若 newsize >  已分配的内存槽的数量,则重新分配内存槽数组(扩容)。
     * (注意,新的 list 和旧的 list 不在同一个内存起始地址。)
    */
    if (allocated >= newsize && newsize >= (allocated >> 1))
    {
        assert(self->ob_item != NULL || newsize == 0);
        Py_SIZE(self) = 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().
     * The growth pattern is:  0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ...
     * 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.
     */
    /**
     * 内存分配方案,获取新的内存槽的数量。
    */
    new_allocated = (size_t)newsize + (newsize >> 3) + (newsize < 9 ? 3 : 6);
    if (new_allocated > (size_t)PY_SSIZE_T_MAX / sizeof(PyObject *))
    {
        PyErr_NoMemory();
        return -1;
    }

    if (newsize == 0)
        new_allocated = 0;
    /**
     * 新的内存槽数组的字节总数。
    */
    num_allocated_bytes = new_allocated * sizeof(PyObject *);
    /**
     * (1)创建新的内存槽数组;
     * (2)将旧内存槽数组中的数据拷贝到新的内存槽数组中;
     * (3)释放旧的内存槽数组。
     * obmalloc.c PyMem_realloc() 函数。
    */
    items = (PyObject **)PyMem_Realloc(self->ob_item, num_allocated_bytes);
    if (items == NULL)
    {
        PyErr_NoMemory();
        return -1;
    }
    /**
     * 将新的状态更新到 list 对象中。
    */
    self->ob_item = items;
    Py_SIZE(self) = newsize;
    self->allocated = new_allocated;
    return 0;
}

5、insert() 源码实现

(1)过程简述

  • 判断是否需要重新对 list 的内存槽数组(ob_item)进行重新分配。若需要,则申请新的内存槽数组、将旧内存槽数组中的数据拷贝到新的内存槽数组中,释放旧的内存槽数组。
  • 将 where 之后的元素全部向后挪一位。
  • 将 new item 放到 where 的位置上。

(2)append() 函数实际上调用的是 ins1() 函数。

int PyList_Insert(PyObject *op, Py_ssize_t where, PyObject *newitem)
{
    if (!PyList_Check(op))
    {
        PyErr_BadInternalCall();
        return -1;
    }
    return ins1((PyListObject *)op, where, newitem);
}

(3)ins1() 源码

static int ins1(PyListObject *self, Py_ssize_t where, PyObject *v)
{
    /**
     * 返回当前已用的内存槽的数量,即:list 中已有的元素的数量。
    */
    Py_ssize_t i, n = Py_SIZE(self);
    PyObject **items;
    if (v == NULL)
    {
        PyErr_BadInternalCall();
        return -1;
    }
    if (n == PY_SSIZE_T_MAX)
    {
        PyErr_SetString(PyExc_OverflowError,
                        "cannot add more objects to list");
        return -1;
    }
    /**
     * 调整内存槽数组。
    */
    if (list_resize(self, n + 1) < 0)
        return -1;

    if (where < 0)
    {
        where += n;
        if (where < 0)
            where = 0;
    }
    if (where > n)
        where = n;
    items = self->ob_item;
    /**
     * 将 where 之后的数据向后挪一位。
    */
    for (i = n; --i >= where;)
        items[i + 1] = items[i];
    Py_INCREF(v);
    items[where] = v;
    return 0;
}

四、拓展

下面是两种创建空 list 的方法,哪一个效率更高呢?

empty_list = []
empty_list = list()

答案是前者效率更高,因为前者是内置的C函数,可以直接调用;后者是 python 的函数调用,会创建 stack 和参数检查,比较浪费时间。

 

(SAW:Game Over!)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值