我最近遇到了这个问题:
sys.getsizeof([0] * 3) # 80
sys.getsizeof([0, 0, 0]) # 120
sys.getsizeof([0 for i in range(3)]) # 88
检查过
Python 3.10.4。需要澄清的是,这种行为并不是所有版本的 Python 的特征,而只是大约3.9.19到3.10.14(但这并不精确)。
真正的问题是这里发生了什么以及为什么相同的列表权重不同?
import sys
list_1 = [0] * 3
list_2 = [0, 0, 0]
list_3 = [0 for i in range(3)]
print(list_1) # > [0, 0, 0]
print(list_2) # > [0, 0, 0]
print(list_3) # > [0, 0, 0]
print(list_1 == list_2 and list_2 == list_3 and list_3 == list_1) # > True
print(sys.getsizeof(list_1)) # > 80
print(sys.getsizeof(list_2)) # > 120
print(sys.getsizeof(list_3)) # > 88
假设第一个数组在复制后在内存中存储对同一对象的三个引用是合乎逻辑的,并且解释器以某种方式对此进行了优化。但其他数组不也会发生同样的情况吗?毕竟,Python 缓存的是从 -5 到 256 的数字,这意味着对缓存的数字 0 的三个相同引用应该存储在其他列表中。但是,问题来了:为什么对相同数字的三个引用与三个相同。引用不同的数字?同时小于列表 2 和 3 中的数字?
list_4 = list(range(3))
print(list_4) # > [0, 1, 2]
print(sys.getsizeof(list_4)) # > 80
[0] * <константа>- 建立一个精确订购尺寸的列表。[0, 0, 0, ...]- 通过一次调用构建一个列表list.extend,该列表分配具有性能裕度的空间。[0 for i in range(<константа)]list.append- 通过循环调用来构建列表。再次强调,为了性能的考虑,空间分配时留有余量。但该库存的计算方式略有不同。详细信息如下。
[0] * <константа>该表达式
[0] * <константа>始终编译相同的内容并导致调用BINARY_MULTIPLY:BINARY_MULTIPLY冒险来了,它选择一个恰好符合所需大小(n)的列表并用零填充它。也就是说,列表大小始终为56 + 8n。list_repeat文字
[0, 0, 0, ...]根据零的数量,文字的编译方式有所不同:
[],[0]并由构建精确列表的[0, 0]调用进行处理。BUILD_LIST在其他情况下,编译器存储一个带有零的元组(不是列表(!))并LIST_EXTEND在空列表上调用它。LIST_EXTEND- 通用程序。如果您在循环中调用它并将数据以小块的形式添加到列表中,那么它应该会很快。可以证明,如果它准确地为元素分配空间,则在大型列表上添加将开始减慢。因此列表的增长与它的大小大致成正比。内部令人困惑的代码list_resize是造成奇怪尖峰的原因。注意152后面的120:[0 for i in range(<константа)]编译器平等对待所有常量。例如
[0 for i in range(4)]:创建一个空列表并用零 (
LIST_APPEND) 填充。列表的大小不是预先计算的;列表是在这个过程中不断增长的。它再次突飞猛进地增长,否则就会出现刹车。再次,令人困惑的代码list_resize。但这里调用了其他参数并导致尺寸略有不同:注意:就性能而言,此选项是最差的:列表的多次重新分配,Python 中的显式循环。只是一场噩梦!