← 返回题目列表

Python 对象的 ==、is 和 hash 有什么关系?

高频 中等 第 6 / 21 题 更新于 2026/07/27
对象比较__eq____hash__ishash

简化版

is 比较两个变量是否指向同一个对象,== 比较对象是否值相等,具体逻辑可由 __eq__ 定义。可哈希对象必须保证相等对象哈希值相等,所以重写 __eq__ 时要认真考虑 __hash__

详细版

示例:

a = [1, 2]
b = [1, 2]

print(a == b)  # True
print(a is b)  # False

两个列表内容相等,但不是同一个对象。

自定义对象默认比较身份:

class User:
    def __init__(self, user_id):
        self.user_id = user_id

u1 = User(1)
u2 = User(1)
print(u1 == u2)  # False

如果希望按字段比较,需要实现 __eq__

class User:
    def __init__(self, user_id):
        self.user_id = user_id

    def __eq__(self, other):
        if not isinstance(other, User):
            return NotImplemented
        return self.user_id == other.user_id

哈希规则:

如果 a == b,那么 hash(a) 必须等于 hash(b)

如果对象可变,通常不适合作为字典 key 或 set 元素。

完整版教学

一、is 比较身份

is 判断两个引用是不是指向同一个对象。

a = []
b = a
c = []

print(a is b)  # True
print(a is c)  # False

ab 指向同一个列表;c 是另一个新列表。

常见正确用法:

if value is None:
    ...

判断 None 时应该用 is,因为 None 是单例对象。

二、== 比较值相等

== 会调用对象的相等比较逻辑。

[1, 2] == [1, 2]

列表实现了按元素比较,所以结果是 True

自定义类如果不实现 __eq__,默认行为通常接近身份比较。

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
Point(1, 2) == Point(1, 2)  # False

要按值比较,就实现 __eq__

三、eq 的返回策略

推荐写法:

def __eq__(self, other):
    if not isinstance(other, Point):
        return NotImplemented
    return self.x == other.x and self.y == other.y

返回 NotImplemented 表示当前比较逻辑不支持对方类型,让 Python 尝试其他路径。

如果直接对陌生类型返回 False,在某些反向比较场景下不够友好。

四、hash 用在哪里

哈希用于字典和集合:

d = {}
d[key] = value

s = {item}

字典和集合需要根据哈希值快速定位元素。

所以作为 key 的对象必须可哈希,并且哈希值在生命周期内应该稳定。

可变列表不能做 key:

{[1, 2]: "value"}  # TypeError

元组如果内部都是可哈希元素,通常可以做 key:

{(1, 2): "value"}

五、eqhash 要一致

如果:

a == b

那么必须满足:

hash(a) == hash(b)

否则字典和集合会出现逻辑混乱。

例如:

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

    def __hash__(self):
        return hash(self.user_id)

如果 user_id 创建后不再变化,这样是合理的。

六、可变对象为什么不适合哈希

如果对象放进 set 后,参与哈希的字段变了,集合就可能再也找不到它。

u = User(1)
s = {u}
u.user_id = 2

如果 __hash__ 依赖 user_id,修改后哈希位置变了,但集合内部结构没有跟着重排。

因此可哈希对象最好是不可变值对象。可变业务对象一般不要自定义可变字段哈希。

七、常见误区与追问

记忆钩子:is 看身份,== 看相等语义,hash 看能否稳定放进哈希表。三者不是一回事,但 ==hash 必须遵守同一个相等契约。

  • 误区:is== 只是写法不同。 is 比较两个名字是否指向同一个对象,== 会调用相等比较逻辑;两个内容相同的列表 == 可以为真,但 is 通常为假。
  • 追问:为什么相等对象必须哈希相等? 字典和集合先用哈希定位桶,再用相等判断确认对象;如果 a == bhash(a) != hash(b),同一个逻辑 key 可能落到不同位置。
  • 误区:重写 __eq__ 后不用管 __hash__ Python 会把这类对象默认变成不可哈希,除非你显式提供与相等逻辑一致的 __hash__,这是为了保护 dict/set 的正确性。
  • 追问:为什么列表不可哈希但元组有时可哈希? 列表可变,哈希不稳定;元组不可变,但只有当元组内部所有元素都可哈希时,元组整体才可哈希。
  • 误区:哈希值唯一代表对象。 哈希可能冲突,dict/set 仍需要用 __eq__ 做最终确认;哈希的目标是快速缩小查找范围,不是提供全局唯一 ID。
  • 追问:业务对象按 id 相等是否合理? 如果业务语义认为同一个 user_id 就是同一用户,可以按 id 实现 __eq____hash__;前提是参与哈希的 id 在对象生命周期内保持稳定。

八、加强记忆

is 看是不是同一个对象,== 看值是否相等,hash 服务字典和集合。重写 __eq__ 就要想清楚 __hash__:相等对象必须哈希相等,参与哈希的字段最好不可变。面试里用列表的 ==is 对比例子最容易讲明白。