Java 对一些值类型(包括整数)进行了特殊处理,以便它们按值存储(而不是像其他所有内容一样通过对象引用)。 Python 不会对此类类型进行特殊处理,因此将 n 分配给 列表中的许多条目(或其他普通 Python 容器)不必进行复制。
编辑:请注意,引用总是对对象,而不是“对变量”——在 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 中的两个条目仍然引用前一个。
array.array(来自 Python 标准库模块 array)与列表非常不同:它保留同质类型的紧凑副本,每个项目占用的位数与存储该类型值的副本所需的位数一样少类型。所有普通容器都保留引用(在 C 编码的 Python 运行时内部实现为指向 PyObject 结构的指针:在 32 位构建中,每个指针占用 4 个字节,每个 PyObject 至少 16 个左右[包括指向类型的指针、引用计数, 实际值和 malloc 向上取整]),数组没有(因此它们不能是异构的,除了少数基本类型之外不能有项目等)。
例如,一个包含 1000 个项目的容器,所有项目都是不同的小整数(每个项目的值可以容纳 2 个字节),将大约 2,000 个字节的数据作为 array.array('h'),但大约 20,000 个字节作为 @ 987654329@。但是,如果所有项目都是相同的数字,则数组仍将占用 2,000 个字节的数据,列表将仅占用 20 个左右 [[在每种情况下,您都必须为容器对象添加大约 16 或 32 个字节正确的,除了数据的内存]]。
然而,虽然问题是“数组”(即使在标签中),但我怀疑它的 arr 实际上是一个数组——如果是,它不能存储 (2**32)*2 (最大 int数组中的值是 32 位),并且实际上不会观察到问题中报告的内存行为。所以,这个问题实际上可能是关于一个列表,而不是一个数组。
编辑:@ooboo 的评论提出了许多合理的后续问题,而不是试图压缩评论中的详细解释,我将其移至此处。
不过,这很奇怪——毕竟,这是怎么回事?
对存储的整数的引用?
id(variable) 给出一个整数,即
参考本身是一个整数,不是
使用整数更便宜?
CPython 将引用存储为指向 PyObject 的指针(用 Java 和 C# 编写的 Jython 和 IronPython 使用这些语言的隐式引用;用 Python 编写的 PyPy 具有非常灵活的后端,可以使用许多不同的策略)
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 整数的单个副本(保留几个小的预定义 C 数组PyObject 形式的整数值,可随时使用或在需要时重用),但通常不会特意寻找其他现有的可重用对象。
但是例如,同一函数中相同的字面常量很容易编译为对函数常量表中单个常量对象的引用,所以这是一个很容易完成的优化,我相信每个当前的 Python 实现都可以执行它。
有时很难记住 Python 是一种一门语言,它有几个实现可能(合法且正确地)在很多细节上有所不同——每个人,包括像我这样的学究,在谈论流行的 C 编码实现时,倾向于只说“Python”而不是“CPython”(在这种情况下,区分语言和实现是最重要的;-)。不过,区别非常重要,值得不时重复一遍。