Python list 实现原理

Python list 实现原理

我们通过本文描述CPython实现 list 列表对象,Cpython是python最常用的实现。

在Python中List 功能非常强大,同时,它的实现方式也是非常有趣。

接下来,通过一个简单的python脚本演示,list中追加 integer 数据,并将它结果打印出来。

>>> l = []
>>> l.append(1)
>>> l.append(2)
>>> l.append(3)
>>> l
[1, 2, 3]
>>> for e in l:
...   print e
... 
1
2
3

正如你看到的样子,列表list是可迭代的(iterable)

List 数据结构

在CPython中列表List是通过C语言的结构体(structure)来表示。

ob_item是指向列表元素的指针列表。

allocated是在内存中分配的插槽数

typedef struct {
    PyObject_VAR_HEAD
    PyObject **ob_item;
    Py_ssize_t allocated;
} PyListObject;

List 初始化过程

当一个空数组初始化时,发生了什么

arguments: size of the list = 0
returns: list object = []
PyListNew:
    nbytes = size * size of global Python object = 0
    allocate new list object
    allocate list of pointers (ob_item) of size nbytes = 0
    clear ob_item
    set list's allocated var to 0 = 0 slots
    return list object 

重要的是要注意分配的插槽和列表大小之间的差异。 列表的大小与len(l)相同。 分配的插槽数是已在内存中分配的数量。 通常,您会看到分配的大小可能大于size。 这是为了避免每次将新元素添加到列表时都需要调用realloc。 稍后我们将详细介绍。

Append

当我们建一个integer追加到list中,l.append(1),将会发生什么? 一个C内部方法 app1() 会被调用。

arguments: list object, new element
returns: 0 if OK, -1 if not
app1:
    n = size of list
    call list_resize() to resize the list to size n+1 = 0 + 1 = 1
    list[n] = list[0] = new element
    return 0

观察下 list_resize(),它过度分配了内存以避免调用list_resize太多时间。 列表的增长方式为:0、4、8、16、25、35、46、58、72、88,...

arguments: list object, new size
returns: 0 if OK, -1 if not
list_resize:
    new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6) = 3
    new_allocated += newsize = 3 + 1 = 4
    resize ob_item (list of pointers) to size new_allocated
    return 0

现在分配了4个slot来包含元素,第一个slot是整数1。您可以在下图中看到l [0]指向我们刚刚附加的整数对象。 虚线正方形表示已分配但尚未使用的插槽。

Appand 操作的时间复杂度 O(1)

image

我们继续添加一个元素:l.append(2)。 使用n + 1 = 2调用list_resize,但是由于分配的大小为4,因此无需分配更多的内存。 当我们再添加2个整数时,也会发生同样的事情:l.append(3),l.append(4)。 下图显示了到目前为止的内容。

image

Insert

让我们在位置1处插入一个新的整数(5):l.insert(1,5),看看内部发生了什么。 函数ins1() 被调用

arguments: list object, where, new element
returns: 0 if OK, -1 if not
ins1:
    resize list to size n+1 = 5 -> 4 more slots will be allocated
    starting at the last element up to the offset where, right shift each element 
    set new element at offset where
    return 0
image

虚线正方形表示已分配但尚未使用的插槽。 在这里,分配了8个插槽,但列表的大小或长度仅为5。

Insert 操作的复杂度为O(n)

Pop

当您弹出最后一个元素:l.pop()时,将调用listpop()。 在listpop()内部调用list_resize,如果新大小小于分配大小的一半,则列表将缩小。

arguments: list object
returns: element popped
listpop:
    if list empty:
        return null
    resize list with size 5 - 1 = 4. 4 is not less than 8/2 so no shrinkage
    set list object size to 4
    return last element

Pop 操作的复杂度为 O(1).

image

您可以看到插槽4仍指向整数,但重要的是列表的大小现在为4。

让我们再弹出一个元素。 在list_resize()中,size – 1 = 4 – 1 = 3小于分配的插槽的一半,因此列表缩小为6个插槽,并且列表的新大小现在为3。

您可以观察到插槽3和4仍指向一些整数,但重要的是列表的大小现在为3。

image

Remove

Python列表对象具有删除特定元素的方法:l.remove(5)。底层函数 listremove() 被调用。

arguments: list object, element to remove
returns none if OK, null if not
listremove:
    loop through each list element:
        if correct element:
            slice list between element's slot and element's slot + 1
            return none
    return null

要切片列表并删除元素,将调用list_ass_slice(),很有趣的是它如何工作。 在这里,低偏移量是1,高偏移量是2,因为我们要删除位置1处的元素5。

arguments: list object, low offset, high offset
returns: 0 if OK
list_ass_slice:
    copy integer 5 to recycle list to dereference it
    shift elements from slot 2 to slot 1
    resize list to 5 slots
    return 0

Remove操作的复杂度为O(n).

image

参考:

你可能感兴趣的:(Python list 实现原理)