Python如何处理小整数的加法?

25 投票
3 回答
1438 浏览
提问于 2025-04-16 18:11

我最近在玩 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 是怎么做到的呢?

3 个回答

2

据我所知,id和参数的大小没有关系。它必须返回一个在整个生命周期内唯一的标识符。而且,如果两个参数不同时存在,它可以返回相同的结果。

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)
17

Python会在某些数字范围内保留一组int对象。当你在这个范围内创建一个数字时,实际上你得到的是一个已经存在的对象的引用。我猜这是为了提高效率。

对于那些不在这个范围内的数字,每次你尝试创建一个时,似乎都会得到一个新的对象。

$ 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之间的整数保留一个 整数对象数组,当你在这个范围内 创建一个整数时,你实际上只是 得到了一个现有对象的引用。 所以理论上可以改变数字1的值。我怀疑在这种情况下,Python的行为是未定义的。:-)

强调是我的观点

撰写回答