首页 > 解决方案 > 是什么导致 [*a] 过度分配?

问题描述

显然list(a)没有过度分配,[x for x in a]在某些时候[*a]过度分配,并且一直过度分配?

最大尺寸 n=100

以下是从 0 到 12 的大小 n 以及三种方法的结果大小(以字节为单位):

0 56 56 56
1 64 88 88
2 72 88 96
3 80 88 104
4 88 88 112
5 96 120 120
6 104 120 128
7 112 120 136
8 120 120 152
9 128 184 184
10 136 184 192
11 144 184 200
12 152 184 208

像这样计算,可在 repl.it 重现,使用 Python 3. 8

from sys import getsizeof

for n in range(13):
    a = [None] * n
    print(n, getsizeof(list(a)),
             getsizeof([x for x in a]),
             getsizeof([*a]))

那么:这是如何工作的?如何[*a]过度分配?实际上,它使用什么机制从给定的输入创建结果列表?它是否使用迭代器a并使用类似的东西list.append?源代码在哪里?

Colab 使用生成图像的数据和代码。)

放大到更小的 n:

尺寸高达 n=40

缩小到更大的 n:

尺寸高达 n=1000

标签: pythonpython-3.xlistcpythonpython-internals

解决方案


[*a] 在内部执行 C 等效项

  1. 做一个新的,空的list
  2. 称呼newlist.extend(a)
  3. 退货list

因此,如果您将测试扩展到:

from sys import getsizeof

for n in range(13):
    a = [None] * n
    l = []
    l.extend(a)
    print(n, getsizeof(list(a)),
             getsizeof([x for x in a]),
             getsizeof([*a]),
             getsizeof(l))

在线尝试!

你会看到 和 的结果getsizeof([*a])l = []; l.extend(a); getsizeof(l)一样的。

这通常是正确的做法;当extend您通常希望稍后添加更多内容时,对于通用解包类似,假设多个内容将一个接一个地添加。[*a]不是正常情况;Python 假设有多个项目或可迭代对象被添加到list( [*a, b, c, *d]) 中,因此过度分配可以节省常见情况下的工作。

相比之下,list由单个、预先确定大小的可迭代对象 (with list()) 构造的 a 在使用过程中可能不会增长或缩小,并且过度分配还为时过早,除非另有证明;Python 最近修复了一个错误,该错误使构造函数即使对于已知大小的输入也会过度分配

至于list推导,它们实际上等同于重复append的 s,因此您在一次添加一个元素时会看到正常过度分配增长模式的最终结果。

需要明确的是,这些都不是语言保证。这就是 CPython 实现它的方式。Python 语言规范通常不关心特定的增长模式list(除了从最后保证摊销O(1) appends 和pops )。如评论中所述,具体实现在 3.9 中再次更改;虽然它不会影响[*a],但它可能会影响其他情况,以前“构建tuple单个项目的临时,然后extend使用tuple”现在变成 的多个应用程序LIST_APPEND,当发生过度分配以及计算中的数字时,这可能会发生变化。


推荐阅读