在谈到一些Python琐事时,我常常在我的Python演讲中展示像print(5 is 7 - 2, 300 is 302 - 2)
这样的东西。今天我意识到这个例子在Python 3.7中运行时会产生一个(对我来说)意想不到的结果。
我们知道从-5到255的数字在内部缓存Python 3 docs - PyLong_FromLong,也可以在早期的API文档中找到。
is
运算符(如文档Python 3 docs - is operator中所述)测试对象标识,即它使用id()
函数来确定当值匹配时产生True
。
id()
函数保证在对象的生命周期内返回一个唯一且恒定的值(也在文档Python 3 docs - id()中描述)。
所有这些规则都会为您提供以下结果(许多Python编码器都知道):
Python 2.7:
>>> print(5 is 7 - 2, 300 is 302 - 2)
True False
Python 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 300
产生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
is
没有改变。语言语义的任何部分都没有改变;您所比较的对象是否是同一个对象从未指定过行为。你的is
比较的两个方面恰好恰好是同一个对象。这是恒定折叠优化的变化的结果。
初始生成的代码对象的co_consts
重用单个对象来获得等效的原子常量。 (我说“等效”而不是“相等”,因为1和1.0不等价。)这与整数从-5到256的缓存效果不同,它只适用于单个代码对象。 Previously,将302 - 2
转换为300
的编译时优化过程发生在字节码窥孔优化器中,它在初始co_consts
生成后启动,并且不执行相同的常量重用。
在CPython 3.7中,这个优化过程是从字节码窥孔优化器到新的AST优化器的moved。 AST优化器在初始生成代码对象的co_consts
之前生效,因此常量重用现在适用于结果。
您可以通过执行类似的操作来查看常规重用对旧Python版本的影响
>>> 300 is 300
True
它甚至在CPython 2.7或3.6上产生True
,尽管300超出了小整数缓存的范围。您可以通过确保您所比较的常量最终位于不同的代码对象中来阻止常量重用:
>>> (lambda: 300)() is 300
False
这会在任何版本的CPython上生成False
,即使新的优化器更改也是如此。但是,它在PyPy上生成True
,因为PyPy有自己的优化行为,而PyPy的行为好像所有相等的整数都由相同的整数对象表示。