1

python中list的实现

  • Author : Jasper Yang

  • School : Bupt

先挂个英文版的原文链接 Laurent Luce's Blog 这个作者还是可以的,我又发现了他的另外一篇关于dict的实现,后面的博文再进行介绍。

在python中实现list是十分有趣的事情,list在现实使用中是那么的强大而令人喜爱,所以,下面让我们一起来一探究竟。

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

如你所见,list是可迭代的

List object C structure

一个list对象在 CPython 中是以如下的数据结构保存的。ob_item存储了一系列指向数据的指针。allocated里面存储的是该list在内存分配的大小(slots)

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

List initialization

看看当我们初始化一个list是会发生什么,e.g. l =[]

    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 

很重要的一点,我们需要注意到 allocated slots (内存分配的空间大小)和list的size的区别。list的size和 len(l) 是一样的。allocated slots的个数就是内存中分配了得slot(可以理解成内存的block)个数。你会很经常看到 allocated 比size还要大。这个是为了避免多次的重新分配内存空间(realloc c函数),后面我们会介绍更多。

Append

我们往list里append数据后会发生什么呢~?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()。它超量地分配了比所需的内存更多的内存空间。

增长模式如下: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分配给了这个list去存储指针,你可以看到l[0]指向了我们刚刚append进去的整数对象。虚线方块代表了没用到的slot。

append

We continue by adding one more element: l.append(2). list_resize is called with n+1 = 2 but because the allocated size is 4, there is no need to allocate more memory. Same thing happens when we add 2 more integers: l.append(3), l.append(4). The following diagram shows what we have so far.

我们继续append更多元素:l.append(2)。list_resize() 被调用了,n = n + 1。但是因为n还是小于 allocated = 4 ,所以没有必要去重新分配内存空间,同样的我们继续append 3 和 4。结果不变。

append2

Insert

让我们在位置1的地方插入整数5: l.insert(1,5),ins1() 会被调用。

insert

现在分配的内存slot个数变成了8,这个增长的规律和我们之前讲的一模一样。

Pop

当我们使用弹出:l.pop(),listpop()会被调用,并且如果list的size小于allocated的一半时,list会收缩,也就是allocated会变小。

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

你可以看到slot 4 仍然指向了原来的整数区域,但是list的size已经缩小了,也就是slot4已经不属于这个list了。

当我们再pop一次后发现,size已经小于allocated的一半了,于是allocated变成了6。

Remove

python 的 list 还有个特殊的移除元素的方法:l.remove(5) ,这里移除的不是第5个slot而是那个指向的值是5的slot。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并移除元素(也就是上面代码中的slice),会调用list_ass_slice()。过程如下。

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

希望你们能从我翻译的这篇文章中学到东西,提高自己~

paper done 2017/04/21

jasperyang
203 声望58 粉丝

Highest purpose is Hacking...