← 返回题目列表

Python 中 is 和 == 有什么区别?

高频 简单 第 3 / 22 题 更新于 2026/07/25
Pythonis==对象身份

简化版

== 比较的是值是否相等,底层会调用对象的相等性逻辑;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 Noneis Trueis 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.0True,因为数值相等;但 1 is 1.0 一定是 False,因为整数对象和浮点对象不是同一个对象。

表达式比较维度典型结果
[1, 2] == [1, 2]True
[1, 2] is [1, 2]身份False
a is a身份True
1 == 1.0True

三、为什么 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 为例,小整数常见缓存范围是 -5256,但这个范围是实现细节,不是 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,小整数和字符串缓存只当解释器优化知识点,不当判断依据。