缓存的整数,Python 3.7中的is运算符和id()

tam*_*gal 7 python python-3.7

print(5 is 7 - 2, 300 is 302 - 2)在谈论一些Python琐事时,我曾经在Python演讲中展示过类似的内容。今天,我意识到在Python 3.7中运行时,此示例产生了一个(对我而言)意外的结果。

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

is操作者(如在该文档中描述的Python 3文档-是操作者)检测该对象的身份,即,其使用id()函数来确定和产率True时的值相匹配。

id()保证该函数在对象的生存期内返回唯一且恒定的值(也在docs Python 3 docs-id()中进行了描述)。

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

Python 2.7:

>>> print(5 is 7 - 2, 300 is 302 - 2)
True False
Run Code Online (Sandbox Code Playgroud)

Python 3.6:

>>> print(5 is 7 - 2, 300 is 302 - 2)
True False
Run Code Online (Sandbox Code Playgroud)

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

>>> print(5 is 7 - 2, 300 is 302 - 2)
True True
Run Code Online (Sandbox Code Playgroud)

我试图了解原因,但是在Python源中找不到任何提示...

id(302 - 2)总是产生不同的值,所以我想知道为什么302 - 2 is 300产生Trueis操作员如何知道这些值相同?这在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
Run Code Online (Sandbox Code Playgroud)

use*_*ica 9

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

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

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


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

>>> 300 is 300
True
Run Code Online (Sandbox Code Playgroud)

True尽管300不在小整数缓存的范围内,但即使在CPython 2.7或3.6上也可以生成。您可以通过确保要比较的常量最终位于单独的代码对象中来防止常量重用:

>>> (lambda: 300)() is 300
False
Run Code Online (Sandbox Code Playgroud)

False即使使用了新的优化程序,它也可以在任何版本的CPython上生成。但是,它True在PyPy上生成,因为PyPy具有其自己的优化行为,并且PyPy的行为就像所有相等的整数都由同一个整数对象表示一样。