深入Python列表的内部实现

本文将介绍列表在 CPython中的实现,因为毕竟Cpython 又是 Python 最为常用的实现。

Python 中的列表非常强大,看看它的内部实现机制是怎么样的,一定非常有趣。

下面是一段 Python 脚本,在列表中添加几个整数,然后打印列表。


  1. >>> l = [] 
  2.  
  3. >>> l.append(1) 
  4.  
  5. >>> l.append(2) 
  6.  
  7. >>> l.append(3) 
  8.  
  9. >>> l 
  10.  
  11. [1, 2, 3] 
  12.  
  13. >>> for e in l: 
  14.  
  15. ...   print e 
  16.  
  17. ... 
  18.  
  19.  
  20.  
  21. 3  

可以发现,列表是一个迭代器。

列表对象的 C 语言结构体

Cpython 中的列表实现类似于下面的 C 结构体。ob_item 是指向列表对象的指针数组。allocated 是申请内存的槽的个数。


  1. typedef struct { 
  2.  
  3.     PyObject_VAR_HEAD 
  4.  
  5.     PyObject **ob_item; 
  6.  
  7.     Py_ssize_t allocated; 
  8.  
  9. } PyListObject;  

列表初始化

看看初始化一个空列表的时候发生了什么,例如:l = []。


  1. arguments: size of the list = 0 
  2.  
  3. returns: list object = [] 
  4.  
  5. PyListNew: 
  6.  
  7.     nbytes = size * size of global Python object = 0 
  8.  
  9.     allocate new list object 
  10.  
  11.     allocate list of pointers (ob_item) of size nbytes = 0 
  12.  
  13.     clear ob_item 
  14.  
  15.     set list's allocated var to 0 = 0 slots 
  16.  
  17.     return list object  

要分清列表大小和分配的槽大小,这很重要。列表的大小和 len(l) 的大小相同。分配槽的大小是指已经在内存中分配了的槽空间数。通常分配的槽的大小要大于列表大小,这是为了避免每次列表添加元素的时候都调用分配内存的函数。下面会具体介绍。

Append 操作

向列表添加一个整数:l.append(1) 时发生了什么?调用了底层的 C 函数 app1()。


  1. arguments: list object, new element 
  2.  
  3. returns: 0 if OK, -1 if not 
  4.  
  5. app1: 
  6.  
  7.     n = size of list 
  8.  
  9.     call list_resize() to resize the list to size n+1 = 0 + 1 = 1 
  10.  
  11.     list[n] = list[0] = new element 
  12.  
  13.     return 0  

下面是 list_resize() 函数。它会多申请一些内存,避免频繁调用 list_resize() 函数。列表的增长模式为:0,4,8,16,25,35,46,58,72,88……


  1. arguments: list object, new size 
  2.  
  3. returns: 0 if OK, -1 if not 
  4.  
  5. list_resize: 
  6.  
  7.     new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6) = 3 
  8.  
  9.     new_allocated += newsize = 3 + 1 = 4 
  10.  
  11.     resize ob_item (list of pointers) to size new_allocated 
  12.  
  13.     return 0  

现在分配了 4 个用来装列表元素的槽空间,并且第一个空间中为整数 1。如下图显示 l[0] 指向我们新添加的整数对象。虚线的方框表示已经分配但没有使用的槽空间。

列表追加元素操作的平均复杂度为 O(1)。

继续添加新的元素:l.append(2)。调用 list_resize 函数,参数为 n+1 = 2, 但是因为已经申请了 4
个槽空间,所以不需要再申请内存空间。再添加两个整数的情况也是一样的:l.append(3),l.append(4)。下图显示了我们现在的情况。 

Insert 操作

在列表偏移量 1 的位置插入新元素,整数 5:l.insert(1,5),内部调用ins1() 函数。


  1. arguments: list object, where, new element 
  2.  
  3. returns: 0 if OK, -1 if not 
  4.  
  5. ins1: 
  6.  
  7.     resize list to size n+1 = 5 -> 4 more slots will be allocated 
  8.  
  9.     starting at the last element up to the offset where, right shift each element 
  10.  
  11.     set new element at offset where 
  12.  
  13.     return 0  

虚线的方框依旧表示已经分配但没有使用的槽空间。现在分配了 8 个槽空间,但是列表的大小却只是 5。

列表插入操作的平均复杂度为 O(n)。

Pop 操作

取出列表最后一个元素 即l.pop(),调用了 listpop() 函数。在 listpop() 函数中会调用 list_resize 函数,如果取出元素后列表的大小小于分配的槽空间数的一半,将会缩减列表的大小。


  1. arguments: list object 
  2.  
  3. returns: element popped 
  4.  
  5. listpop: 
  6.  
  7.     if list empty: 
  8.  
  9.         return null 
  10.  
  11.     resize list with size 5 - 1 = 4. 4 is not less than 8/2 so no shrinkage 
  12.  
  13.     set list object size to 4 
  14.  
  15.     return last element  

列表 pop 操作的平均复杂度为 O(1)。

可以看到 pop 操作后槽空间 4 依然指向原先的整数对象,但是最为关键的是现在列表的大小已经变为 4。

继续 pop 一个元素。在 list_resize() 函数中,size – 1 = 4 – 1 = 3 已经小于所分配的槽空间大小的一半,所以缩减分配的槽空间为 6,同时现在列表的大小为 3。

可以看到槽空间 3 和 4 依然指向原先的整数,但是现在列表的大小已经变为 3。

Remove 操作

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


  1. arguments: list object, element to remove 
  2.  
  3. returns none if OK, null if not 
  4.  
  5. listremove: 
  6.  
  7.     loop through each list element: 
  8.  
  9.         if correct element: 
  10.  
  11.             slice list between element's slot and element's slot + 1 
  12.  
  13.             return none 
  14.  
  15.     return null  

为了做列表的切片并且删除元素,调用了 list_ass_slice() 函数,它的实现方法比较有趣。我们在删除列表位置 1 的元素 5 的时候,低位的偏移量为 1 同时高位的偏移量为 2.


  1. arguments: list object, low offset, high offset 
  2.  
  3. returns: 0 if OK 
  4.  
  5. list_ass_slice: 
  6.  
  7.     copy integer 5 to recycle list to dereference it 
  8.  
  9.     shift elements from slot 2 to slot 1 
  10.  
  11.     resize list to 5 slots 
  12.  
  13.     return 0  

列表 remove 操作的复杂度为 O(n)。

作者:佚名

来源:51CTO

时间: 2024-09-14 20:15:54

深入Python列表的内部实现的相关文章

list-!求助:python 列表读取时的问题

问题描述 !求助:python 列表读取时的问题 project euler problem 81题我写了下面的代码,origin里面是5x5的矩阵,运行结果正常但是我把origin换成一个80x80的矩阵的时候,在读取383568的时候却读取成了326735我没有找出代码的问题,但是在codepad和terminal上运行也是错误答案求助大牛啊!!大恩不言谢 origin = ['13167323410318' '20196342965150' '630803746422111' '53769

python列表操作实例_python

本文实例讲述了python列表操作的方法.分享给大家供大家参考. 具体实现方法如下: 复制代码 代码如下: class Node:    """Single node in a data structure"""      def __init__(self, data):       """Node constructor"""              self._data = da

python 列表中的n个字典的更新问题

问题描述 python 列表中的n个字典的更新问题 弄了一下午还是没想明白: eg: d1 = [{"eslid":"5A-15-D4-99","nw1":"51-02-01-66","nw3":"50",'rfpower': '50', 'netid': '65', 'apid': 1, 'version': '51', 'battery': '30', 'reverse': '0'}

isinstance-你好,关于python列表生成式有个问题想请教一下

问题描述 你好,关于python列表生成式有个问题想请教一下 x='abc'y=123想要输出a=1b=2c=3我是这么写的: x='abc'y=123[k+'='+v for k in x for v in y if isinstance(ystr) else v for v in str(y)]但是提示出错,请问应该怎么改 解决方案 z = str(y).join('{0}={1}'.format(x[i] z[i]) for i in range(3)) 解决方案二: y是字符串的话 '1

python列表中的字符串长成&amp;amp;#39;&amp;amp;quot;book&amp;amp;quot;&amp;amp;#39;。。。

问题描述 python列表中的字符串长成'"book"'... 列表是 a = ['"book"','"desk"','"pen"']的样子,怎样把每个元素外边的不必要的''除去呢?除了从新敲一遍,谢谢! 解决方案 strip或者字符串截取都可以哦 解决方案二: 直接用编辑器字符串替换掉 解决方案三: python_忽略大小写对字符串列表排序python列表 字符串 转换Python:字符串.列表和元组

哪位大神能帮我看看这个关于python列表的相关问题?O(∩_∩)O谢谢

问题描述 哪位大神能帮我看看这个关于python列表的相关问题?O(∩_∩)O谢谢 看看b,c这两个数是否在a的这几组数的范围内,在j就加一,不再就不加 j=0 a = [[23, 26], [45, 67], [45, 69]] b = 34 c = 46 for i in a: if i[0] <= 某数 <=i[1]: 就输出j=j+1 同时打破这个循环 else: 比下一对还是回去执行? 像c这样的数,我不想遍历a,b不再这几组数的范围内,遍历就遍历吧.知道这么个意思,就是写不出来,打

python 列表转化为字符串的两种方式

Python 列表转化为字符串的两种方式 (1)方式一 Python代码   >>> juice=['orange','a','b']   >>> ''.join(juice)   'orangeab'       (2)方式二: Python代码   >>> juice=['orange','a','b']   >>> content='%s'*len(juice) % tuple(juice)   >>> pri

Python 列表(List)操作方法详解_python

列表是Python中最基本的数据结构,列表是最常用的Python数据类型,列表的数据项不需要具有相同的类型.列表中的每个元素都分配一个数字 - 它的位置,或索引,第一个索引是0,第二个索引是1,依此类推.Python有6个序列的内置类型,但最常见的是列表和元组.序列都可以进行的操作包括索引,切片,加,乘,检查成员.此外,Python已经内置确定序列的长度以及确定最大和最小的元素的方法. 一.创建一个列表只要把逗号分隔的不同的数据项使用方括号括起来即可.如下所示: 复制代码 代码如下: list1

Python列表list数组array用法实例解析_python

本文以实例形式详细讲述了Python列表list数组array用法.分享给大家供大家参考.具体如下: Python中的列表(list)类似于C#中的可变数组(ArrayList),用于顺序存储结构.   创建列表   复制代码 代码如下: sample_list = ['a',1,('a','b')]   Python 列表操作 复制代码 代码如下: sample_list = ['a','b',0,1,3]   得到列表中的某一个值 复制代码 代码如下: value_start = sampl