脚本专家 发表于 2026-6-14 11:00:00

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

很多Python初学者在遇到 is 和 == 时会感到困惑:为什么同样是判断相等,有时输出 True,有时输出 False?这两者的本质区别是什么?本文将从底层实现到实际场景,彻底理清这两个操作符的差异。

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

用代码验证:

a =
b =
print(a == b)# True,值相等
print(a is b)# False,不同对象

c = a
print(c is a)# True,同一对象


== 的本质是调用对象的 __eq__ 方法。自定义类可以重写 __eq__ 来决定比较逻辑。如果未定义 __eq__,则默认使用 object 的 __eq__,此时等价于 is。

class Person:
    def __init__(self, name, age):
      self.name = name
      self.age = age
    def __eq__(self, other):
      if not isinstance(other, Person):
            return False
      return self.name == other.name and self.age == other.age

p1 = Person('小明', 25)
p2 = Person('小明', 25)
print(p1 == p2)# True,自定义__eq__生效


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

Python 解释器对某些小整数和字符串做了缓存。例如整数 -5 到 256 会被预创建并复用,因此这两个范围内的整数用 is 可能为 True;超出范围则每次创建新对象。字符串驻留机制也会让一些简单字符串共享同一对象。但这些是 CPython 实现细节,不应依赖。

a = 256
b = 256
print(a is b)# True,缓存命中

a = 257
b = 257
print(a is b)# False,超出缓存范围

s1 = "hello"
s2 = "hello"
print(s1 is s2)# 通常True,字符串驻留


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

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

**实战:哨兵对象**

_MISSING = object()

def get_value(data, key, default=_MISSING):
    try:
      return data
    except KeyError:
      if default is _MISSING:
            raise
      return default

这样既能区分“键存在但值为 None”和“键不存在”。

**常见陷阱**
- 陷阱1:用 is 比较整数状态码。

# 错误
if code is 200: ...
# 正确
if code == 200: ...


- 陷阱2:用 is 比较从外部输入的字符串。

# 错误
if username is 'admin': ...
# 正确
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。

热心网友5 发表于 2026-6-14 11:05:00

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

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

热心网友6 发表于 2026-6-16 22:15:00

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

感谢楼主的详细讲解!之前对 `is` 和 `==` 的用法总是模棱两可,特别是看到小整数缓存和字符串驻留的例子才意识到自己踩过坑。你举的哨兵对象例子很实用,用 `_MISSING` 配合 `is` 来区分 `None` 和未设置,这个技巧我准备加进自己的工具函数里了。另外提醒一下:对于自定义类的 `__eq__`,记得也要实现 `__hash__`,否则作为字典键时会有问题(不过这和本文主题无关)。再次感谢分享!

热心网友1 发表于 2026-6-17 00:10:02

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

楼主讲得非常透彻!特别是那个“钞票”的比喻和哨兵对象的实战例子,一下子把抽象概念落地了。之前我总在字符串驻留上踩坑,看完才明白不该依赖 is 来比较普通字符串。建议再加一条:自定义类重写 __eq__ 时最好同步重写 __hash__,否则作为字典键会出问题——不过这可能属于另一个话题了。感谢分享!
页: [1]
查看完整版本: Python is vs ==:身份比较与值比较的核心区别及避坑指南