25

我最近在摆弄id并意识到 (c?)Python 做了一些非常明智的事情:它确保小整数总是具有相同的id.

>>> a, b, c, d, e = 1, 2, 3, 4, 5
>>> f, g, h, i, j = 1, 2, 3, 4, 5
>>> [id(x) == id(y) for x, y in zip([a, b, c, d, e], [f, g, h, i, j])]
[True, True, True, True, True]

但后来我想知道数学运算的结果是否也是如此。原来是:

>>> nines = [(x + y, 9) for x, y in enumerate(reversed(range(10)))]
>>> [id(x) == id(y) for x, y in nines]
[True, True, True, True, True, True, True, True, True, True]

似乎它在 n=257 开始失败......

>>> a, b = 200 + 56, 256
>>> id(a) == id(b)
True
>>> a, b = 200 + 57, 257
>>> id(a) == id(b)
False

但有时它仍然适用于更大的数字:

>>> [id(2 * x + y) == id(300 + x) for x, y in enumerate(reversed(range(301)))][:10]
[True, True, True, True, True, True, True, True, True, True]

这里发生了什么?python是如何做到这一点的?

4

3 回答 3

20

你掉进了一个不常见的陷阱:

id(2 * x + y) == id(300 + x)

这两个表达式2 * x + y300 + x没有重叠的生命周期。这意味着 Python 可以计算左侧,获取其 id,然后在计算右侧之前释放整数。当 CPython 释放一个整数时,它会将其放在已释放整数的列表中,然后在下次需要时将其重新用于不同的整数。因此,即使计算结果非常不同,您的 id 也会匹配:

>>> x, y = 100, 40000
>>> id(2 * x + y) == id(300 + x)
True
>>> 2 * x + y, 300 + x
(40200, 400)
于 2011-05-23T19:03:59.777 回答
17

intPython以一定的数量保存一个对象池。当您在该范围内创建一个时,您实际上会获得对预先存在的一个的引用。我怀疑这是出于优化原因。

对于该池范围之外的数字,每当您尝试制作一个新对象时,您似乎都会取回一个新对象。

$ python
Python 3.2 (r32:88445, Apr 15 2011, 11:09:05) 
[GCC 4.5.2 20110127 (prerelease)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> x = 300
>>> id(x)
140570345270544
>>> id(100+200)
140570372179568
>>> id(x*2)
140570345270512
>>> id(600)
140570345270576

来源

PyObject* PyInt_FromLong(long ival) 返回值:新引用。创建一个值为 ival 的新整数对象。

当前的实现为 -5 到 256 之间的所有整数保留一个整数对象数组,当您在该范围内创建一个 int 时,您实际上只是取回了对现有对象的引用。所以应该可以改变 1 的值。我怀疑 Python 在这种情况下的行为是未定义的。:-)

强调我的

于 2011-05-23T18:43:19.267 回答
2

AFAIK,id 与参数的大小无关。它必须返回一个生命周期的唯一标识符,并且它可以为两个不同的参数返回相同的结果,如果它们不同时存在的话。

于 2011-05-23T18:40:51.157 回答