在使用 Python 的过程中,很多开发者可能遇到过这个令人困惑的行为:
python
复制编辑
a = 500
b = 500
print(a is b) # ??? True 还是 False?
你可能预期这是 False,因为 500 超出了 Python 小整数缓存的范围(-5 到 256),结果却是 True。这背后到底发生了什么?本文将深入剖析 Python 的整数缓存机制和编译优化原理,揭示“真假 is”的秘密。
🔢 一、Python 中的整数缓存机制
Python(CPython 实现)对某些常用对象,如 None、True、False 和 小整数,做了缓存,以提升性能和内存复用。
✅ 小整数缓存范围
Python 会缓存所有 [-5, 256] 范围内的整数对象:
python
复制编辑
a = 100
b = 100
print(a is b) # True ✅
a = -5
b = -5
print(a is b) # True ✅
a = 257
b = 257
print(a is b) # False ❌
这个机制确保了频繁使用的小整数不会重复创建多个对象,属于 CPython 的性能优化。
🧠 二、令人困惑的情况:500 is 500 是 True?
如果你执行:
python
复制编辑
a = 500
b = 500
print(a is b)
你可能惊讶地发现它是 True,这和“超出缓存范围就不是同一个对象”的理论不符。其实,这并不是缓存机制在发挥作用,而是 Python 的编译优化机制在“捣鬼”。
⚙️ 三、Python 编译器的常量折叠优化
Python 的字节码编译器在编译 .py 文件或 REPL 命令时,会做常量折叠(constant folding)。简单说,如果在代码中多次使用了相同的字面值常量,编译器可能只创建一个对象实例,提高效率。
例如:
python
复制编辑
a = 500
b = 500
这两行代码被 Python 认为是两个对同一个常量 500 的引用,因此可能被优化为一个对象:
python
复制编辑
>>> a = 500
>>> b = 500
>>> a is b
True # 因为编译器复用了同一个字面量对象
但只要你稍微“动态”一点:
python
复制编辑
a = int("500")
b = int("500")
print(a is b) # False
或者:
python
复制编辑
def get_500():
return 500
a = get_500()
b = get_500()
print(a is b) # False
这时候就不会触发常量折叠,两个对象自然不是同一个。
🧪 四、用代码实验证明差异
你可以用 id() 函数对比两个对象的地址:
python
复制编辑
print(id(500))
print(id(int("500")))
或者封装到函数中,避免常量折叠:
python
复制编辑
def test():
a = 500
b = 500
print(a is b) # False,取决于作用域是否隔离
test()
📝 五、is 和 == 的区别
最后提醒一下:
==比较的是 值是否相等is比较的是 对象是否为同一个内存地址
python
复制编辑
a = 1000
b = 1000
print(a == b) # True ✅ 值相等
print(a is b) # False ❌ 不是同一个对象
使用场景:
- 判断值用
== - 判断是否为同一个对象(如
None)用is
📌 总结
| 测试值 | 表达式 | 是否同对象 (is) | 原因说明 |
|---|---|---|---|
100 | a = 100; b = 100 | ✅ True | 小整数缓存 |
500 | a = 500; b = 500 | ✅/❌ 可能是 True | 编译器常量折叠 |
500 | a = int("500") 等 | ❌ False | 运行时创建新对象 |
None | a is None | ✅ True | None 是单例对象 |
📘 延伸阅读
- 官方文档:docs.python.org/3/reference…
- CPython 源码:
Include/longobject.h和Objects/longobject.c中定义了整数缓存区
📢 结语:
下次当你看到 a is b 为 True 时,请别急着下结论“它是缓存”,也可能是编译优化“偷偷地帮了你”。理解 Python 背后的机制,才能真正写出“心中有数”的代码。
如果这篇文章对你有帮助,欢迎点赞、评论、关注我~ 🚀