
Python 的 is 比较对象身份(内存地址),而整数小常量(-5~256)在 CPython 中被缓存;但超出该范围的整数是否复用,取决于编译阶段的字节码优化策略——交互式输入逐行编译无跨行优化,而脚本文件整体编译可能合并相同字面量。
python 的 `is` 比较对象身份(内存地址),而整数小常量(-5~256)在 cpython 中被缓存;但超出该范围的整数是否复用,取决于编译阶段的字节码优化策略——交互式输入逐行编译无跨行优化,而脚本文件整体编译可能合并相同字面量。
在 Python 中,is 运算符用于判断两个变量是否引用同一个对象(即内存地址完全相同),而非值相等(这应使用 ==)。这一行为背后的关键机制是 CPython 解释器对整数对象的内存管理策略。
小整数缓存机制(Integer Caching)
CPython 对范围在 [-5, 256] 内的所有整数进行了全局缓存(位于 Objects/longobject.c 中的 small_ints 数组)。这意味着:
>>> a = 100 >>> b = 100 >>> a is b True >>> id(a) == id(b) True
无论 a 和 b 如何赋值,只要值在此范围内,它们必然指向同一预分配对象。
超出缓存范围的行为差异:脚本 vs 交互式解释器
对于 300 这类大于 256 的整数,CPython 不保证缓存,其是否复用对象取决于代码如何被编译为字节码:
-
✅ 脚本文件(如 main.py):
整个模块被一次性解析、优化并编译。CPython 的字节码生成器(如 peephole optimizer)可能将同一作用域内重复出现的相同字面量(如连续两行 a = 300; b = 300)优化为指向同一个常量对象:# main.py a = 300 b = 300 print(a is b) # 输出 True(常见于 Python 3.8+ 默认编译行为)
-
❌ 交互式解释器(REPL):
每行代码被独立编译(PyRun_String + PyEval_EvalCode),缺乏跨行优化能力。因此 a = 300 和 b = 300 被视为两次独立的字面量加载,分别创建新对象:>>> a = 300 >>> b = 300 >>> a is b False >>> id(a), id(b) (140234567890123, 140234567890456) # 地址不同
? 验证技巧:使用 dis 模块查看字节码差异
import dis def script_version(): a = 300; b = 300; return a is b def repl_version(): return (lambda: 300)() is (lambda: 300)() dis.dis(script_version) # 可能显示 LOAD_CONST 两次指向同一常量索引
⚠️ 重要注意事项
- 此行为是 CPython 实现细节,非语言规范:其他 Python 实现(如 PyPy、Jython)或未来版本可能不同。
- 绝不依赖 is 比较数字或字符串字面量:它不可靠且违背语义意图。始终用 == 判断值相等。
- 禁用优化验证:运行 python3 -B -O main.py(禁用字节码缓存与优化)可能使脚本行为趋近 REPL。
- 唯一安全场景:仅对 None, True, False 等单例对象使用 is(如 x is None)。
总结
a is b 在脚本中返回 True、在 REPL 中返回 False,本质反映的是编译时优化粒度差异,而非内存模型矛盾。理解这一机制有助于规避隐蔽 Bug,但更关键的是建立正确编程习惯:用 == 比较值,用 is 仅判别明确的单例对象。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











