查看: 101|回复: 1

Python is vs ==:身份比较与值比较的核心区别及避坑指南

[复制链接]
发表于 2 小时前 | 显示全部楼层 |阅读模式
很多Python初学者在遇到 is 和 == 时会感到困惑:为什么同样是判断相等,有时输出 True,有时输出 False?这两者的本质区别是什么?本文将从底层实现到实际场景,彻底理清这两个操作符的差异。

== 比较的是对象的“值”,is 比较的是对象的“身份”(内存地址)。可以用生活中的例子类比:== 相当于判断两张钞票的购买力是否相同,is 相当于判断这两张是不是同一张钞票。

用代码验证:
  1. a = [1, 2, 3]
  2. b = [1, 2, 3]
  3. print(a == b)  # True,值相等
  4. print(a is b)  # False,不同对象
  5. c = a
  6. print(c is a)  # True,同一对象
复制代码

== 的本质是调用对象的 __eq__ 方法。自定义类可以重写 __eq__ 来决定比较逻辑。如果未定义 __eq__,则默认使用 object 的 __eq__,此时等价于 is。
  1. class Person:
  2.     def __init__(self, name, age):
  3.         self.name = name
  4.         self.age = age
  5.     def __eq__(self, other):
  6.         if not isinstance(other, Person):
  7.             return False
  8.         return self.name == other.name and self.age == other.age
  9. p1 = Person('小明', 25)
  10. p2 = Person('小明', 25)
  11. print(p1 == p2)  # True,自定义__eq__生效
复制代码

is 是原子操作,不能被重载。它直接比较 id(),等价于 id(a) == id(b)。

Python 解释器对某些小整数和字符串做了缓存。例如整数 -5 到 256 会被预创建并复用,因此这两个范围内的整数用 is 可能为 True;超出范围则每次创建新对象。字符串驻留机制也会让一些简单字符串共享同一对象。但这些是 CPython 实现细节,不应依赖。
  1. a = 256
  2. b = 256
  3. print(a is b)  # True,缓存命中
  4. a = 257
  5. b = 257
  6. print(a is b)  # False,超出缓存范围
  7. s1 = "hello"
  8. s2 = "hello"
  9. print(s1 is s2)  # 通常True,字符串驻留
复制代码

**何时用 is?**
黄金法则:和 None 比较永远用 is。因为 None 是单例,is 速度更快且不受 __eq__ 重写影响。PEP 8 也明确推荐。
其他适用场景:
- 哨兵对象检测(区分“未设置”与“值为 None”)。
- 单例模式验证(检查两个变量是否指向同一实例)。
- 循环链表检测(快慢指针判断指向同一节点)。
- 缓存装饰器(通过 is 判断哨兵对象是否匹配)。

**何时绝对不要用 is?**
- 比较数值时:不要依赖小整数缓存。
- 比较字符串时:不要依赖字符串驻留。
- 比较列表、字典、集合等可变容器时:is 比较的是对象身份,而非内容。

**实战:哨兵对象**
  1. _MISSING = object()
  2. def get_value(data, key, default=_MISSING):
  3.     try:
  4.         return data[key]
  5.     except KeyError:
  6.         if default is _MISSING:
  7.             raise
  8.         return default
复制代码
这样既能区分“键存在但值为 None”和“键不存在”。

**常见陷阱**
- 陷阱1:用 is 比较整数状态码。
  1. # 错误
  2. if code is 200: ...
  3. # 正确
  4. if code == 200: ...
复制代码

- 陷阱2:用 is 比较从外部输入的字符串。
  1. # 错误
  2. if username is 'admin': ...
  3. # 正确
  4. if username == 'admin': ...
复制代码

- 陷阱3:is not 与 not x is None 的区别。推荐用 x is not None,可读性更好。

- 陷阱4:NaN 的比较。float('nan') 与自身 == 比较返回 False,但 is 返回 True。正确检查用 math.isnan()。

- 陷阱5:可变对象的 == 结果会随时间变化(如列表追加元素后),但 is 结果不变。

**速查表**
| 场景 | 推荐操作 | 说明 |
|------|----------|------|
| 与 None 比较 | x is None | PEP 8 推荐,最快最安全 |
| 比较数值 | == | 不要依赖整数缓存 |
| 比较字符串 | == | 不要依赖字符串驻留 |
| 比较列表/字典/集合 | == | 比的是内容 |
| 单例模式验证 | is | 判断是否是同一实例 |
| 哨兵对象检测 | is | 区分“值为None”和“未设置” |
| 类型检查 | isinstance() | 比 type() is 更好 |
| 布尔值检查 | 直接用 if x: | 比 if x is True: 好 |

总结:== 比较值(可重写),is 比较身份(不可重写)。处理 None 一律用 is;处理普通数据一律用 ==。牢记这些原则,能避免大量隐蔽 Bug。
回复

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Python is vs ==:身份比较与值比较的核心区别及避坑指南

楼主总结得非常透彻,尤其是用钞票类比“==看面值,is看是不是同一张”,新手一听就懂。哨兵对象的例子也很实用,很多人在处理默认值时会踩`None`和“未设置”混淆的坑,用`_MISSING`加`is`判断确实是优雅解法。 想请教一个小点:对于自定义类,如果既重写了`__eq__`,又希望保留身份比较的能力(比如在某些缓存场景下),通常怎么设计比较合理?是额外提供一个`is_same()`方法,还是提醒调用者必要时用`id()`?
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-6-14 13:44 , Processed in 0.023209 second(s), 17 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部