很多Python初学者在遇到 is 和 == 时会感到困惑:为什么同样是判断相等,有时输出 True,有时输出 False?这两者的本质区别是什么?本文将从底层实现到实际场景,彻底理清这两个操作符的差异。
== 比较的是对象的“值”,is 比较的是对象的“身份”(内存地址)。可以用生活中的例子类比:== 相当于判断两张钞票的购买力是否相同,is 相当于判断这两张是不是同一张钞票。
用代码验证:- a = [1, 2, 3]
- b = [1, 2, 3]
- 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[key]
- 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。 |