首页 > 解决方案 > Python 3.7 中的缓存整数、`is` 运算符和 `id()`

问题描述

print(5 is 7 - 2, 300 is 302 - 2)在谈论一些 Python 琐事时,我曾经在我的 Python 演讲中展示过类似的东西。今天我意识到这个例子在 Python 3.7 中运行时产生了一个(对我来说)意想不到的结果。

我们知道从 -5 到 255 的数字在Python 3 文档内部缓存 - PyLong_FromLong也可以在早期的 API 文档中找到。

is运算符(如 docs Python 3 docs - is operator 中所述测试对象身份,即它使用id()函数来确定它并True在值匹配时产生。

id()函数保证在其生命周期内为对象返回一个唯一且恒定的值(也在文档Python 3 文档 - id()中进行了描述)。

所有这些规则都会为您提供以下结果(许多 Python 编码人员都知道):

蟒蛇 2.7:

>>> print(5 is 7 - 2, 300 is 302 - 2)
True False

蟒蛇 3.6:

>>> print(5 is 7 - 2, 300 is 302 - 2)
True False

但是,Python 3.7 的行为有所不同:

>>> print(5 is 7 - 2, 300 is 302 - 2)
True True

我试图理解为什么,但我在 Python 源代码中找不到任何提示......

id(302 - 2)总是产生不同的值,所以我想知道为什么302 - 2 is 300yield True。操作员如何is知道这些值是相同的?对于 Python 3.7 中的整数比较,这是否以某种方式重载?

>>> id(300)
140059023515344

>>> id(302 - 2)
140059037091600

>>> id(300) is id(302 - 2)
False

>>> 300 is 302 - 2
True

>>> id(300) == id(302 -2)
True

>>> id(302 - 2)
140059037090320

>>> id(302 - 2)
140059023514640

标签: pythonpython-3.7

解决方案


is没有改变。语言语义的任何部分都没有改变;您要比较的对象是否是同一个对象从未指定过行为。您比较的两个方面is现在恰好是同一个对象。这是常数折叠优化变化的结果。

代码对象的初始生成co_consts将单个对象重用于等效的原子常量。(我说“等价”而不是“等价”,因为 1 和 1.0 不等价。)这与缓存从 -5 到 256 的整数不同,它只适用于单个代码对象。以前,转换302 - 2为的编译时优化过程300发生在字节码窥孔优化器中,该优化器在初始co_consts生成后启动,并且不会进行相同的持续重用。

在 CPython 3.7 中,此优化过程已从字节码窥孔优化器移至新的 AST 优化器。AST 优化器在代码对象的初始生成之前生效co_consts,因此现在不断重用适用于结果。


您可以通过执行类似的操作来查看不断重用对旧 Python 版本的影响

>>> 300 is 300
True

True甚至在 CPython 2.7 或 3.6 上产生,尽管 300 超出了小整数缓存的范围。您可以通过确保要比较的常量出现在单独的代码对象中来防止常量重用:

>>> (lambda: 300)() is 300
False

这会False在任何版本的 CPython 上产生,即使有新的优化器更改。但是,它True在 PyPy 上产生,因为 PyPy 有自己的优化行为,并且 PyPy 的行为就像所有相等的整数都由同一个整数对象表示。


推荐阅读