Python中使用的优化:列表和元组

在Python中,有两种相似的类型-列表(列表)和元组(元组)。 两者之间最著名的区别是元组是不可变的。

您不能更改元组中的对象:

>>> a = (1,2,3) >>> a[0] = 10 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'tuple' object does not support item assignment 

但是您可以在元组中修改可变对象:

 >>> b = (1,[1,2,3],3) >>> b[1] [1, 2, 3] >>> b[1].append(4) >>> b (1, [1, 2, 3, 4], 3) 

在CPython(标准解释器)内部,列表和元组被实现为指向Python对象的指针(链接)表,即 实际上,它们不会将对象彼此相邻存储。 从列表中删除对象时,将删除对该对象的引用。 如果其他人引用了该对象,它将继续存在于内存中。

元组


尽管元组在代码中不那么常见且不那么流行,但这是Python经常用于内部目的的非常基本的类型。

您可能没有注意到,但是在以下情况下使用元组:

  • 使用参数或参数(它们存储为元组)
  • 从函数返回两个或多个变量
  • 迭代字典中的值键
  • 使用字符串格式

通常,最简单的脚本使用数千个元组:

 >>> import gc >>> def type_stats(type_obj): ... count = 0 ... for obj in gc.get_objects(): ... if type(obj) == type_obj: ... count += 1 ... return count ... >>> type_stats(tuple) 3136 >>> type_stats(list) 659 >>> import pandas >>> type_stats(tuple) 6953 >>> type_stats(list) 2455 

空列表与空元组


空元组的工作方式类似于单例,即 运行中的Python脚本的内存中始终只有一个空元组。 所有空元组仅引用同一对象,这是可能的,因为元组是不可变的。 这种方法节省了大量内存,并加快了使用空元组的过程。

 >>> a = () >>> b = () >>> a is b True >>> id(a) 4409020488 >>> id(b) 4409020488 >>> #  CPython,  id    . 

但这不适用于列表,因为它们可以更改:

 >>> a = [] >>> b = [] >>> a is b False >>> id(a) 4465566920 >>> id(b) 4465370632 

优化元组的内存分配


为了减少内存碎片并加快元组的创建,Python重用了已删除的旧元组。 如果一个元组由少于20个元素组成并且不再使用,则Python会将其放置在一个特殊列表中,而不是将其删除,该列表存储了可供重用的元组。

此列表分为20个组,其中每个组是大小为n的元组的列表,其中n为0到20。每个组最多可以存储2,000个自由元组。 第一组仅存储一个元素,并且是一个空元组的列表。

 >>> a = (1,2,3) >>> id(a) 4427578104 >>> del a >>> b = (1,2,4) >>> id(b) 4427578104 

在上面的示例中,我们可以看到a和b在内存中具有相同的地址。 这是由于我们立即获取了一个相同大小的免费元组。

优化列表的内存分配


由于列表可能会发生变化,因此无法再启动与元组相同的优化。 尽管如此,列表还是针对空列表使用了类似的优化。 如果删除了空列表,则将来也可以重用它。

 >>> a = [] >>> id(a) 4465566792 >>> del a >>> b = [] >>> id(b) 4465566792 

调整大小清单


为了避免不断调整列表大小的开销,Python不会在每次需要时调整其大小。 而是,每个列表都有一组对用户隐藏的附加单元格,但以后可用于新元素。 一旦隐藏的单元格结束,Python就会为新元素添加额外的空间。 而且他做到了这一点,根据列表的当前大小选择了隐藏单元格的数量-它越大,用于新元素的隐藏插槽越多。

当您尝试在循环中添加许多元素时,此优化特别有用。

列表大小增长模式如下所示:0、4、8、16、25、35、46、58、72、88,...

例如,如果要向具有8个元素的列表中添加一个新元素,则其中将没有空闲单元格,Python将立即将其大小扩展为16个单元格,其中9个将被占用并为用户可见。

Python大小调整公式:

 >>> def get_new_size(n_items): ... new_size = n_items + (n_items // 2 ** 3) ... if n_items < 9: ... new_size += 3 ... else: ... new_size += 6 ... ... return new_size ... >>> get_new_size(9) 16 

速度


如果我们比较这两种类型的速度,那么平均而言,在医院中,元组要比列表稍快。 Raymond Hettinger对于stackoverflow的速度差异有很好的解释。

PS:我是本文的作者,您可以提出任何问题。

Source: https://habr.com/ru/post/zh-CN417783/


All Articles