python - 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 300
yield 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 的整数不同,它只适用于单个代码对象。以前,转换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 的行为就像所有相等的整数都由同一个整数对象表示。
推荐阅读
- flutter - StreamBuilder snapshot.hasError 在键盘显示/隐藏颤动时显示多次
- python - 从 MySQL 表中解密 Fernet 密文
- react-native - 状态更改不重新渲染组件
- docker - docker-compose > v.3 搜索量_来自模拟
- python - Python初学者
- angular - 从订阅到可观察的未定义值
- c - 运行程序“Segmentation fault core dumped”时出错
- android - 在 Android WebView 中的移动和桌面站点之间切换
- swift - 如何命名通过 UActivityViewController 存储到文件应用程序的文件
- mysql - 通过命令 UPDATE 将文本和数据插入一列