从 Python 列表的特性来探究其底层实现机制

 list 之所以好用归功于底层巧妙的设计

列表(list)是 Python 中一个非常重要且常见的数据结构,它有很多易用的特性:可索引([index]),可切片([start, end, step]),能对其中的元素进行增(append、insert、extend)删(pop、remove)改操作。

如果你同时熟悉其他编程语言,比如 C++,你会觉得 Python 列表和 C++ STL 提供的 list 在操作上有些相似。

是的,它们都支持元素的增删改,在代码编写方面,C++ 的 list 不如 Python 列表开发效率高。C++ 中,你可能需要手动对 list 进行迭代,找到合适的元素或位置,然后执行操作。而 Python 中几乎所有操作都可以通过索引或切片来完成。

这样看来,二者底层实现机制应是有所区别的。事实正是如此。

C++ 的 list 是一个双向链表,链表中的节点是动态分配的,节点之间通过指针彼此相连。

这种结构的好处是,通过修改指针的指向,可以高效地在指定位置插入新的元素或删除指定的元素,而基本不影响其他节点。

其弊端就是无法随机访问 list 中的元素。在 list 中查找某个元素时需要从头开始遍历,时间复杂度为 O(n)。

那么,Python 是如何实现 list 数据结构的呢?

Python 3.9.2 FAQ 专门对 list 在 CPython 中的实现进行了简短的回答:

CPython's lists are really variable-length arrays, not Lisp-style linked lists. The implementation uses a contiguous array of references to other objects, and keeps a pointer to this array and the array's length in a list head structure

意思是说,CPython 中的 list 其实是一个变长数组,而非链表。变长数组中保存的是其他对象的引用。

使用这样一种数据结构的好处是,在 list 上执行索引操作所耗费的成本和 list 的尺寸或索引值的大小无关,即复杂度接近常量级。这是很显然的事情,因为这就是 C 或 C++ 中数组的下标访问。

FAQ 中还进一步介绍了该数据结构可能会产生的额外开销:

When items are appended or inserted, the array of references is resized.  Some cleverness is applied to improve the performance of appending items repeatedly; when the array must be grown, some extra space is allocated so the next few times don't require an actual resize.

这个开销是在变长数组 resize 的时候产生的。当我们向 list 中插入或追加元素时,变长数组会根据需要来调整对象指针在数组中的位置,还会对数组大小进行扩充,以容纳更多的指针。

下面我们从代码层面来确认一下 Python 中 list 的实现方式。

在 Python 3.9.2 中,每个 list 对象都是一个 PyListObject 类的实例。

可以看到,PyListObject 中包含了一个 ob_item 的成员,它就是上文所说的变长数组,或者说它指向了那个存放着 PyObject 对象的指针的数组。list 的索引操作就是在这个数组上的随机访问,效率当然高了。

我们再通过 list 的插入操作看一下如何使用这个变长数组。

list_insert() 就是实现插入操作的入口函数。在对入参进行检查后,list_insert 调用了 list_insert_impl()。可以看到,list_insert_impl() 接收的就是插入位置和插入对象两个参数,这和我们之前介绍的 list 的 insert(<index>, <obj>) 方法保持一致。

list_insert_impl() 进一步调用 ins1() 来实现插入逻辑。

ins1() 首先对变长数组的当前空间进行检查,根据需要 resize 变长数组的大小。然后计算实际的插入位置 where。

之后,ins1() 将 obj_item 中 where 后边的元素逐个后移,将新元素 v (是一个指向待插入对象的指针)保存到 where 位置。同时将 v 的引用计数加 1.

使用变长数组这一数据结构,既提升了访问 list 元素的速度,又不显著影响插入或删除元素的速度,在时间和空间利用上做了很好的平衡。

至此,list 的实现方式已基本可以搞清楚了。

大家都知道 Python 开发效率高,而这种高效率凝结了 Python 设计者的聪明智慧。我们享受便利的时候更应该向他们致敬!

(0)

相关推荐

  • 对比 Python 原生切片,讲述 Numpy 数组切片!

    对比 Python 原生切片,讲述 Numpy 数组切片!

  • LeetCode面试系列 第12天:No.977 - 有序数组的平方

    上一篇 LeetCode 面试题中,我们分析了一道集合相关的数学题.现在我们再来看一个排序相关的面试题吧~ Leetcode 今天要给大家分析的面试题是 LeetCode 上第 977 号问题, Le ...

  • UC头条:聊聊python中的list——基本操作

    在学习数据结构的时候,从老师和同学口中得知了python中用于实现线性表的list(列表).在查阅相关资料后,感觉这真是一个有趣又好用的数据结构.于是打算写几篇博客,加深对list原理和使用方法的理解 ...

  • Python的常用库的数组定义及常用操作

    好奇心Log 今天 以下文章来源于阿宗的科研备忘 ,作者阿宗的科研备忘 Python支持的库非常多,这当然是它的一大优势,但是也会给我们实际应用中造成点小小的麻烦:每个库对于数据的定义和运算处理都不同 ...

  • Python列表与元组有什么相同点?基础分享!

    无论从事Python相关工作还是刚刚学习Python,想必大家都听说过Python列表和元祖吧,而且经常有人将他们混为一谈,那么你知道Python列表和元组有什么相同点和不同点吗?我们通过这篇文章来看 ...

  • Python列表与元组有什么作用?入门分享!

    Python数据类型分为七大类,其中最为常见的就是列表和字典,是使用Python必须掌握的基础.那么Python列表和字典有什么不同之处?我们一起来看看吧. 列表 1. 任意对象的有序集合,列表是一组 ...

  • 什么是Python列表?与元组有何区别?

    Python中,有三种内建的数据结构,列表.元组和字典,那么它们之间有什么区别呢?我们通过这篇文章来看看吧. 什么是Python列表? 列表是由一系列按特定顺序排列的元组组成的.在Python中,用[ ...

  • python列表

    列表list是python中最常用的数据类型,其类似于其他语言中的数组,但也有不同,主要的区别在于list中的元素可以是不同的数据类型. 1.列表的创建 创建一个列表,只要用方括号把数据项括起来即可. ...

  • 4.Python列表/元组/集合/字典

    碧茂大数据 前天 4.1 Python列表 · 列表用 [ ] 标识,是Python 最通用的复合数据类型. · 列表用 [ ] 表示,列表具有可嵌套性 4.1.1 Python列表截取 · 列表可以 ...

  • ​Python 3 新特性:类型注解

    前几天有同学问到,这个写法是什么意思: def add(x:int, y:int) -> int:    return x + y 我们知道 Python 是一种动态语言,变量以及函数的参数是不 ...

  • 像这样操作 Python 列表,能让你的代码更优雅

    写 Python 代码,列表的出镜率是相当高的,伴随列表一起出现的往往就是一大堆 for 循环,这样的代码多了看起来非常不简洁.作为一名 Python 程序员,怎么能忍受呢? 那有没有什么好办法呢?除 ...

  • Python 列表的应用场景有哪些?你使用对了吗?

    我们在前几篇文章中依次介绍了列表的特性和用法.列表推导式.列表的底层实现.今天来聊一聊列表在实际开发中的应用场景. 在开发中,选用何种数据结构是由我们面对的数据特征和业务场景决定的. 数据是单个的还是 ...

  • Python数组和Python列表的区别!

    众所周知,Python数据类型分为很多种,其中包括元组.字典.列表等.今天这篇文章主要为大家介绍一下Python数组和Python列表的区别,希望对你们有所帮助. Python中的list是Pytho ...