《Python == 与 is 的真相:从基础语义到底层机制,一篇让新手顿悟、老手沉默的深度解析》
一、开篇:为什么一篇“== 和 is 的区别”能写到 3000 字?
如果你已经写过一段时间 Python,你一定听过一句话:
“== 比较值是否相等,is 比较是否是同一个对象。”
这句话没错,但远远不够。
事实上,== 和 is 的区别,是 Python 世界里最容易被低估、却最能体现语言设计哲学的知识点之一。
它牵涉到:
- Python 的对象模型
- 内存管理机制
- 小整数缓存
- 字符串驻留(interning)
- 可变与不可变对象
- 解释器优化策略
- 甚至影响你写代码的性能与可维护性
我写这篇文章,是因为在过去十几年教学与项目实战中,我见过太多开发者(包括工作 5 年以上的)在这个问题上踩坑。
更关键的是:这个知识点看似简单,却能成为你理解 Python 深层机制的入口。
今天,我们不仅要讲清楚 == 和 is 的区别,还要通过一个“让人当场沉默”的例子,让你真正理解 Python 的对象世界。
二、Python 对象模型:理解 == 和 is 的前提
Python 是一门“万物皆对象”的语言。
- 每个变量都是一个“名字”
- 每个名字指向一个对象
- 对象存储在内存中
- 对象有三个核心属性:
| 属性 | 含义 |
|---|---|
| id() | 对象在内存中的地址 |
| type() | 对象的类型 |
| value | 对象的值 |
理解 == 和 is,本质上就是理解:
- value 是否相等
- id 是否相同
三、基础语义:== 与 is 的官方定义
✅ ==:比较值是否相等(value equality)
底层调用对象的 __eq__ 方法。
a == b # 等价于 a.__eq__(b)
✅ is:比较是否是同一个对象(identity equality)
底层比较两个对象的 id:
a is b # 等价于 id(a) == id(b)
四、基础示例:看似简单,但埋着坑
✅ 示例 1:数字
a = 1000
b = 1000
print(a == b) # True
print(a is b) # False
为什么?
因为 Python 对 小整数(-5 到 256)做了缓存,但对大整数不会。
✅ 示例 2:字符串
a = "hello"
b = "hello"
print(a == b) # True
print(a is b) # True(大多数情况下)
为什么?
因为 Python 会对部分字符串做 驻留(interning) 优化。
但注意:不是所有字符串都驻留。
五、让人当场沉默的例子:看完你会怀疑人生
下面这个例子,我在课堂上讲过无数次,每次都能让一半以上的开发者沉默。
✅ 例子:列表中的字符串比较
a = ["hello"] * 3
b = ["hello"] * 3
print(a[0] == b[0]) # True
print(a[0] is b[0]) # ??? 你猜?
大多数人会说:
“应该是 False,因为是两个不同列表里的元素。”
但实际运行:
True
True
为什么?
因为 "hello" 被 Python 驻留了,所有 "hello" 都指向同一个对象。
✅ 再看一个让人沉默的例子
x = "hello world!"
y = "hello world!"
print(x == y) # True
print(x is y) # ??? 你猜?
大多数人会说:
“长字符串不会驻留,所以 is 应该是 False。”
但实际运行:
- 在 CPython、某些版本、某些优化场景下,可能是 True
- 在另一些场景下,可能是 False
也就是说:
你永远不能依赖字符串驻留的行为。
这就是让人沉默的地方。
六、深入底层:为什么 Python 要做这些优化?
✅ 1. 小整数缓存(Small Integer Cache)
Python 频繁使用小整数(循环、索引、计数器),为了性能,解释器提前创建并缓存:
-5 到 256
所以:
a = 100
b = 100
a is b # True
但:
a = 1000
b = 1000
a is b # False
✅ 2. 字符串驻留(String Interning)
Python 会自动驻留:
- 短字符串
- 标识符形式的字符串(变量名、函数名)
- 编译期可确定的字符串
但不会驻留:
- 运行时拼接的字符串
- 包含空格、特殊字符的字符串(不一定)
例如:
a = "hello world"
b = "hello world"
a is b # 可能 True,也可能 False
✅ 3. 可变对象 vs 不可变对象
| 类型 | 可变性 | == | is |
|---|---|---|---|
| list | 可变 | 比较内容 | 比较地址 |
| dict | 可变 | 比较内容 | 比较地址 |
| set | 可变 | 比较内容 | 比较地址 |
| tuple | 不可变 | 比较内容 | 比较地址 |
| str | 不可变 | 比较内容 | 可能驻留 |
| int | 不可变 | 比较值 | 小整数缓存 |
七、实战:== 和 is 在项目中的最佳实践
✅ 1. 判断对象是否为 None
永远用:
if x is None:
不要用:
if x == None:
原因:
- None 是单例对象
- is 更快
- == 可能被对象重载导致意外行为
✅ 2. 判断两个变量是否指向同一对象
例如缓存、单例模式:
if obj is cached_obj:
...
✅ 3. 不要用 is 比较数字或字符串
因为:
- 小整数缓存不可靠
- 字符串驻留不可控
错误示例:
if a is 100:
...
正确写法:
if a == 100:
...
✅ 4. 判断类型时不要用 is
错误:
if type(a) is list:
正确:
if isinstance(a, list):
原因:
- 支持继承
- 更 Pythonic
八、案例实战:一个真实项目中的坑
某次我们在做一个配置系统,代码如下:
if config["mode"] is "debug":
enable_debug()
结果线上出现了:
- 有时进入 debug 模式
- 有时不进入
原因:
"debug"字符串在某些情况下被驻留- 在另一些情况下没有被驻留
- 导致 is 判断不稳定
修复:
if config["mode"] == "debug":
enable_debug()
九、性能对比:== 和 is 的速度差异
简单测试:
import timeit
print(timeit.timeit("a == b", setup="a=100;b=100"))
print(timeit.timeit("a is b", setup="a=100;b=100"))
一般来说:
is更快(直接比较 id)==需要调用__eq__
但性能差异通常不影响实际业务。
十、前沿视角:Python 未来会改变这些行为吗?
随着 Python 解释器不断优化(如 PEP 683、PEP 709),未来可能出现:
- 更 aggressive 的字符串驻留策略
- 更智能的对象缓存
- 更优化的对象比较机制
但有一点不会变:
== 比较值,is 比较身份。
这是 Python 对象模型的核心哲学。
十一、总结:一句话记住 == 和 is
- == 比较值是否相等
- is 比较是否是同一个对象
- 不要用 is 比较数字和字符串
- 判断 None 永远用 is
- 字符串驻留和小整数缓存是优化,不是语言保证
十二、互动:你遇到过哪些 == 和 is 的坑?
我很想听听你的经历:
- 你是否在项目中遇到过 == 和 is 导致的 bug?
- 你是否踩过字符串驻留或小整数缓存的坑?
- 你认为 Python 是否应该让 is 更“可控”?
欢迎在评论区分享你的故事,我们一起交流、一起成长。
如果你愿意,我还可以继续扩展:
✅ 生成流程图解释对象模型
✅ 生成 UML 图解释对象关系
✅ 写一篇《Python 对象模型深度解析》
✅ 写一篇《字符串驻留机制全景图》
你想继续深入哪个方向?

51万+

被折叠的 条评论
为什么被折叠?



