在 Python 中,我们经常会听到“对象必须是可哈希的(hashable)”这样的要求,尤其是在使用集合(set)或字典(dict 的 key)时。那么,究竟什么是“可哈希”呢?

一、哈希的基本概念

1、哈希

哈希(Hash)是一种将任意对象映射为固定长度整数值的方法。这个整数称为“哈希值”(hash value)。

作用:快速比较、快速查找。

原理:哈希值作为索引存储在哈希表(hash table)中,能够在常数时间完成查找。

限制:不同对象可能产生相同哈希值,这叫“哈希冲突”(collision),解释器会在冲突时调用 __eq__() 进一步比较对象是否相等。

2、可哈希

在 Python 中,一个对象被称为“可哈希”(Hashable),必须满足以下条件:

1、实现了 __hash__() 方法,能返回一个整数作为哈希值。

2、实现了 __eq__() 方法,用于比较相等性。

3、在对象的生命周期内,哈希值必须保持不变。

二、可哈希与不可哈希

在 Python 中,所有内置的可变类型(如 list、dict、set)默认都是不可哈希的。这是因为它们的内容可能随时改变,无法保证哈希值的稳定性。

相反,大多数不可变的内置类型默认是可哈希的,但也有例外(例如包含不可哈希元素的元组)。

1、可哈希对象

常见可哈希类型包括:

1、int、float、bool、str

2、tuple(元组,前提是其中的元素也必须可哈希)

3、frozenset(冻结集合,不可变)

示例:

print(hash(42))                # 整数可哈希print(hash("hello"))           # 字符串可哈希print(hash((1, 2, 3)))         # 元组可哈希print(hash(frozenset({1, 2}))) # 冻结集合可哈希

使用内置函数 hash(obj) 可以获取对象的哈希值:

🔹 如果对象是可哈希的,hash() 返回一个整数;

🔹 如果对象不可哈希,则抛出 TypeError。

2、不可哈希对象

所有可变类型默认不可哈希,例如:listdictset

s = [1, 2, 3]print(hash(s))  # ❌ TypeError: unhashable type: 'list'  # list 是可变的,不可哈希

特例提醒:

tuple 中如果包含 list,整体也不可哈希。

三、集合与字典的哈希要求

1、集合(set)

要求元素唯一,集合中元素必须可哈希,底层依赖哈希表。

s = {"a", "b", "c"}   # 字符串可哈希,可以作为集合元素

2、字典(dict)

字典的键必须可哈希,也依赖哈希表。而字典的值可以是任意对象(可哈希或不可哈希)。

d = {"name": "Alice", "age": 20}  # 字符串键可哈希。

四、自定义类的可哈希性

1、默认行为

用户自定义类如果不重写 __eq__() 或 __hash__(),默认是可哈希的:

🔹 哈希值基于对象的 id()

🔹 相等性基于 is(身份比较)。

class Foo: pass
f1, f2 = Foo(), Foo()print(hash(f1), hash(f2))   # 各自的哈希值不同print(f1 == f2)             # False(默认比较身份)

2、自定义 __eq__() 和 __hash__()

如果重写了 __eq__(),必须确保相等对象的哈希值也相等,否则会破坏哈希表逻辑。

class Point:    def __init__(self, x, y):        self.x, self.y = x, y        def __eq__(self, other):        return isinstance(other, Point) and (self.x, self.y) == (other.x, other.y)        def __hash__(self):        return hash((self.x, self.y))  # 保证相等对象哈希值一致
p1, p2 = Point(1, 2), Point(1, 2)print(p1 == p2)             # Trueprint(hash(p1), hash(p2))   # 相等对象 → 哈希值一致

3、一致性原则

在 Python 中,自定义类若需可哈希,必须保证 __eq__ 与 __hash__ 的一致性:

1、相等对象必须有相同的哈希值

如果 a == b 为 True,则必须确保 hash(a) == hash(b)。

否则,集合/字典在查找时可能“找不到”键。

2、不相等对象允许哈希冲突,但最好避免

不相等对象可以拥有相同哈希值(冲突可接受),但不同的哈希值能提升性能,减少冲突。

换句话说:哈希决定对象存放位置,__eq__() 决定对象是否相等。

只有两者协同一致,哈希表才能正常工作。

五、常见误区与易错点

1、不可变类型 ≠ 必然可哈希

tuple 中如果包含了不可哈希元素(例如 list),整体就不可哈希:

t = (1, [2, 3])print(hash(t))  # ❌ TypeError: unhashable type: 'list'

2、浮点数 NaN 的特殊性

float('nan') 是可哈希的,但因为 nan != nan,可能导致集合和字典中出现多个 NaN:

nan1 = float('nan')nan2 = float('nan')print(nan1 == nan2)    # Falses = {nan1, nan2}print(len(s))          # 2

3、重写 __eq__() 却忘记 __hash__()

如果只重写了 __eq__(),Python 会自动将 __hash__ 设为 None,导致对象不可哈希:

class A:    def __eq__(self, other):        return True
a = A()print(hash(a))  # ❌ TypeError: unhashable type: 'A'

4、哈希值依赖可变属性

若 __hash__() 的计算依赖了可变属性,当属性改变后哈希值也随之改变,会破坏集合或字典逻辑,应避免此设计。

📘 小结

在 Python 中,可哈希对象是指哈希值在生命周期内保持稳定并支持相等性比较的对象。常见的不可变类型(如 str、tuple、frozenset)通常是可哈希的,而可变对象(如 list、dict、set)则不可哈希。集合元素和字典键必须是可哈希对象,自定义类若需可哈希,必须保证 __eq__ 与 __hash__ 的一致性。

图片

“点赞有美意,赞赏是鼓励”

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐