Python 中 is 和 == 有什么区别?
简化版
== 比较的是值是否相等,底层会调用对象的相等性逻辑;is 比较的是是否为同一个对象,也就是对象身份是否相同。判断 None 应该用 is None,判断普通值相等通常用 ==。
详细版
看例子:
a = [1, 2]
b = [1, 2]
c = a
print(a == b) # True,值相等
print(a is b) # False,不是同一个对象
print(a is c) # True,指向同一个对象
使用建议:
- 判断值是否相等:用
==。 - 判断是否同一个对象:用
is。 - 判断单例对象:用
is None、is True、is False,其中最常见是is None。 - 不要用
is比较字符串、数字等普通值,即使某些小整数或字符串因为解释器优化看起来返回True。
错误示例:
x = 1000
y = 1000
print(x is y) # 不应该依赖这个结果
这类结果可能受解释器实现、常量折叠、缓存策略影响,不应该作为业务逻辑依据。
完整版教学
一、对象有“值”和“身份”两个维度
Python 对象至少可以从两个维度理解:
- identity:对象身份,表示是不是同一个对象。
- value:对象值,表示内容是否相等。
is 看 identity,== 看 value。
a = {"x": 1}
b = {"x": 1}
print(a == b) # True
print(a is b) # False
两个字典内容一样,所以 == 为真;但它们是两个独立对象,所以 is 为假。id(obj) 可以用来观察对象身份标识,但准确说它是解释器给对象的身份值,不应在语言层面把它绝对等同于“内存地址”。在 CPython 中它常和对象地址有关,在其他实现中不必如此。
a = [1, 2]
b = [1, 2]
c = a
print(id(a) == id(b)) # False
print(id(a) == id(c)) # True
二、== 背后是对象的相等性协议
== 会走对象的相等性实现,通常会调用左操作数的 __eq__,必要时也可能让右操作数参与比较。自定义类可以通过 __eq__ 改变比较行为,所以 == 表达的是“这个类型认为二者是否相等”,不是简单逐字节比较。
class User:
def __init__(self, user_id):
self.user_id = user_id
def __eq__(self, other):
return isinstance(other, User) and self.user_id == other.user_id
u1 = User(1)
u2 = User(1)
print(u1 == u2) # True
print(u1 is u2) # False
这说明 == 关注“业务上是否相等”,is 关注“是否同一个对象”。再看一个数字例子:1 == 1.0 是 True,因为数值相等;但 1 is 1.0 一定是 False,因为整数对象和浮点对象不是同一个对象。
| 表达式 | 比较维度 | 典型结果 |
|---|---|---|
[1, 2] == [1, 2] | 值 | True |
[1, 2] is [1, 2] | 身份 | False |
a is a | 身份 | True |
1 == 1.0 | 值 | True |
三、为什么 None 要用 is 判断
None 是 Python 中的单例对象,全局只有一个。判断一个变量是不是 None,最准确的写法是:
if value is None:
...
不要写:
if value == None:
...
因为 == 可能被对象重载,存在被自定义逻辑干扰的可能;is None 直接判断对象身份,不会被 __eq__ 影响。更关键的是,可读性上 is None 一眼说明你判断的是“没有值”这个单例,而不是业务值相等。
class Weird:
def __eq__(self, other):
return True
w = Weird()
print(w == None) # True,被 __eq__ 干扰
print(w is None) # False,身份判断稳定
记忆钩子:判断单例用
is,判断业务值用==。None是最常见单例,哨兵对象也是同一类问题。
四、小整数和字符串缓存为什么容易误导
你可能见过:
a = 1
b = 1
print(a is b) # True
这是因为解释器可能缓存一些小整数、短字符串或复用常量对象。以 CPython 为例,小整数常见缓存范围是 -5 到 256,但这个范围是实现细节,不是 Python 语言要求你依赖的业务规则。字符串也可能因为驻留机制或编译期常量折叠而复用对象。
反过来:
a = int("1000")
b = int("1000")
print(a == b) # True
print(a is b) # 结果不要依赖
面试中最好表达为:不要用 is 判断数值或字符串相等,除非你明确要判断对象身份。下面这类代码在不同写法、不同解释器、不同运行上下文里都可能变化:
x = "py" + "thon"
y = "python"
print(x is y) # 可能 True,因为编译期优化
x = "".join(["py", "thon"])
y = "python"
print(x is y) # 不应依赖
五、实际代码里怎么选
适合用 is:
if result is None:
...
if obj is sentinel:
...
适合用 ==:
if status == "success":
...
if nums == [1, 2, 3]:
...
if user1 == user2:
...
sentinel 是哨兵对象,常用来区分“用户没有传值”和“用户传了 None”:
_MISSING = object()
def get_value(value=_MISSING):
if value is _MISSING:
return "no value"
return value
哨兵对象适合区分“没有传参数”和“传入了 None”。比如缓存查询函数中,None 可能是合法查询结果,此时用 _MISSING = object() 做默认值更稳。判断哨兵必须用 is,因为你关心的是“是不是同一个唯一对象”,不是值相等。
六、常见误区与追问
- 误区:
is比==更快,所以都用is。 身份判断可能更直接,但语义错了就没有意义;普通值比较必须用==。 - 误区:小整数
a is b为真,说明数字比较可以用is。 这是解释器优化带来的对象复用,不是数值相等的规则。 - 误区:
==一定比较对象内容。 它比较的是类型定义的相等性,可能被__eq__自定义,甚至可能写出反直觉逻辑。 - 追问:为什么
None推荐is None? 因为None是单例,身份判断稳定且不会被__eq__干扰。 - 追问:
is能判断两个列表内容一样吗? 不能;两个独立列表内容相同也不是同一对象,内容比较用==。 - 追问:
is not None和!= None有什么区别? 前者判断身份,后者走相等性协议;判断空值哨兵时前者更准确。
七、加强记忆
把这题记成“两把尺子”:== 是业务相等这把尺,会尊重类型自己的 __eq__;is 是对象身份这把尺,只问是不是同一个对象。写代码时普通值、字符串、列表内容用 ==,None 和自定义哨兵用 is,小整数和字符串缓存只当解释器优化知识点,不当判断依据。