Java 对一些值类型(包括整数)进行特殊处理,以便它们按值存储(而不是像其他所有内容一样按对象引用存储)。 Python 不会对此类类型进行特殊处理,因此可以将 n 分配给多个条目在列表中(或其他普通的 Python 容器)不必复制。
编辑:请注意,引用始终是objects,而不是“变量”——Python(或 Java)中没有“对变量的引用”这样的东西。例如:
>>> n = 23
>>> a = [n,n]
>>> print id(n), id(a[0]), id(a[1])
8402048 8402048 8402048
>>> n = 45
>>> print id(n), id(a[0]), id(a[1])
8401784 8402048 8402048
我们从第一个打印中看到列表中的两个条目a
引用完全相同的对象n
指的是——但是当n
被重新分配,it现在指的是不同的对象,而两个条目a
还是参考上一篇。
An array.array
(来自Python标准库模块array)与列表有很大不同:它保留同类类型的紧凑副本,每个项目占用存储该类型值的副本所需的尽可能少的位。所有普通容器都保留引用(在 C 代码的 Python 运行时内部实现为 PyObject 结构的指针:在 32 位构建上,每个指针占用 4 个字节,每个 PyObject 至少 16 个字节左右 [包括指向类型的指针、引用计数、实际值和 malloc 向上舍入]),数组则不然(因此它们不能是异构的,除了一些基本类型之外不能有项目等)。
例如,一个包含 1000 个项目的容器,其中所有项目都是不同的小整数(每个项目的值可以容纳 2 个字节),将需要大约 2,000 个字节的数据作为array.array('h')
,但大约 20,000 作为list
。但是,如果所有项目的数量相同,则数组仍将占用 2,000 字节的数据,列表将仅占用 20 左右 [[在每种情况下,您都必须为容器对象添加大约 16 或 32 字节正确的,除了数据的内存]]。
然而,虽然问题说“数组”(即使在标签中),但我怀疑它arr
实际上是一个数组——如果是的话,它无法存储 (2**32)*2 (数组中最大的 int 值是 32 位),并且实际上不会观察到问题中报告的内存行为。所以,问题实际上可能是关于列表,而不是数组。
Edit:@ooboo 的评论提出了许多合理的后续问题,我并没有试图在评论中压缩详细的解释,而是将其移至此处。
不过,这很奇怪——毕竟,怎么样?
对存储的整数的引用?
id(variable) 给出一个整数,
引用本身是一个整数,不是
使用整数更便宜吗?
CPython 将引用存储为 PyObject 的指针(Jython 和 IronPython,用 Java 和 C# 编写,使用这些语言的隐式引用;PyPy,用 Python 编写,具有非常灵活的后端,可以使用许多不同的策略)
id(v)
给出(仅在 CPython 上)指针的数值(就像唯一标识对象的便捷方法)。列表可以是异构的(某些项可能是整数,其他项可能是不同类型的对象),因此将某些项存储为 PyObject 的指针和其他不同的项并不是一个明智的选择(每个对象还需要类型指示,并且在 CPython 中,至少引用计数)--array.array
是同构且有限的,因此它可以(并且确实)确实存储项目值的副本而不是引用(这通常更便宜,但不适用于同一项目出现很多的集合,例如稀疏数组,其中绝大多数项目数为 0)。
语言规范完全允许 Python 实现尝试更微妙的优化技巧,只要它保持语义不变,但据我所知,目前没有一个可以解决这个特定问题(您可以尝试破解 PyPy 后端,但不要这样做)如果检查 int 与非 int 的开销超过了预期的收益,请不要感到惊讶)。
另外,如果我
分配的2**64
改为每个插槽
分配 n,当 n 持有 a
参考2**64
?当发生什么
我只写1?
这些是每个实现都完全允许做出的实现选择的示例,因为保留语义并不难(因此假设甚至 3.1 和 3.2 在这方面的行为也可能不同)。
当您使用 int 文字(或任何其他不可变类型的文字)或产生此类类型结果的其他表达式时,由实现决定是无条件创建该类型的新对象,还是花费一些时间检查这些对象以查看是否存在可以重用的现有对象。
在实践中,CPython(我相信其他实现,但我不太熟悉它们的内部结构)使用一个足够的副本small整数(以 PyObject 形式保留一些预定义的 C 数组,其中包含一些小整数值,以便在需要时使用或重用),但通常不会特意寻找其他现有的可重用对象。
但是,例如,同一函数中的相同文字常量可以轻松编译为对函数常量表中单个常量对象的引用,因此这是一种非常容易完成的优化,我相信当前的每个 Python 实现都执行它。
有时它比 Python 更难记住一种语言它有几个实现可能(合法且正确地)在许多此类细节上有所不同 - 每个人,包括像我这样的学究,在谈论流行的 C 编码实现时倾向于只说“Python”而不是“CPython”(除了在像这样的上下文中,区分语言和实现是至关重要的;-)。尽管如此,区别is非常重要,并且值得偶尔重复一次。