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

时间:2019-04-02 21:35:53

标签: python python-3.7

在谈论某些Python琐事时,我经常在Python演讲中展示类似 for (var i = _firstIndex; i < _lastIndex; i++) { DataRow dr = dt.NewRow(); dr[0] = i; dr[1] = i + 1; dt.Rows.Add(dr); } rptPaging.DataSource = dt; rptPaging.DataBind(); } protected void Code_TextChanged(object sender, EventArgs e) { BindDataIntoRepeater(); }[my screenshot is here][1] 的内容。今天,我意识到当在Python 3.7中运行时,此示例产生了一个(对我而言)意外的结果。

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

print(5 is 7 - 2, 300 is 302 - 2)运算符(如文档Python 3 docs - is operator中所述)测试对象标识,即它使用is函数确定对象并在值取回时产生id()匹配。

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

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

Python 2.7:

id()

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 False

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

>>> print(5 is 7 - 2, 300 is 302 - 2) True True 总是产生不同的值,所以我想知道为什么id(302 - 2)产生302 - 2 is 300True运算符如何知道这些值相同?这在Python 3.7中是否因整数比较而过载?

is

1 个答案:

答案 0 :(得分:9)

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的行为就像所有相等的整数都由同一个整数对象表示。