Python中可哈希对象需满足哈希值不变且等值对象哈希值相同;默认可哈希的有int、float、str、bytes、None、元素全可哈希的tuple、frozenset;list、dict、set因可变不可哈希;自定义类需同时正确定义__hash__和__eq__。

Python中,set 和 dict 的底层依赖对象的哈希值(hash())来实现快速查找和去重。能否放入 set 或作为 dict 的键,取决于该对象是否“可哈希”——核心是:对象必须满足 哈希值在整个生命周期内不变,且 相等的对象必须有相同的哈希值(即遵守 __hash__ 与 __eq__ 的一致性约定)。
哪些对象默认可哈希?
不可变内置类型通常可哈希:
-
int、float、str、bytes、None - 元组(
tuple)——但仅当其所有元素都可哈希时才可哈希(例如(1, "a", (2, 3))✅;(1, [2])❌) - frozenset(冻结集合)✅;普通
set❌(因可变)
为什么 list、dict、set 不可哈希?
因为它们是可变容器:内容改变后,若哈希值不变,就会破坏哈希表结构(比如键查不到、重复插入);若强制改哈希值,又违反“哈希值不可变”原则。所以 Python 直接禁止它们实现 __hash__(返回 NotImplemented),调用 hash() 会报 TypeError。
例如:
立即学习“Python免费学习笔记(深入)”;
hash([1, 2]) # TypeError: unhashable type: 'list'
hash({'a': 1}) # TypeError: unhashable type: 'dict'
hash({1, 2}) # TypeError: unhashable type: 'set'自定义类如何支持放入 set 或作 dict 键?
需同时定义 __hash__ 和 __eq__,且保证逻辑一致:
-
__hash__应基于不可变属性计算(推荐用hash((a, b, c))元组哈希) -
__eq__必须用相同属性判断相等性 - 若重写了
__eq__但没定义__hash__,Python 会自动将__hash__设为None(即不可哈希)
示例:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
return isinstance(other, Point) and self.x == other.x and self.y == other.y
def __hash__(self):
return hash((self.x, self.y)) # 基于不可变属性构造元组哈希p1 = Point(1, 2)
p2 = Point(1, 2)
print(p1 == p2) # True
print(hash(p1) == hash(p2)) # True
s = {p1, p2}
print(len(s)) # 1 —— 正确去重
常见陷阱与建议
- 不要在
__hash__中使用可变属性(如列表、字典字段),否则哈希值可能随内容变化,导致 set/dict 行为异常 - 若对象逻辑上应不可变,建议在
__init__后禁止修改关键属性(可用__slots__或属性只读封装) - 调试时可用
hasattr(obj, '__hash__') and obj.__hash__ is not None判断是否可哈希 - 想临时用可变对象作键?可转成不可变表示,例如
dict→frozenset(items()),list→tuple()(确保元素可哈希)










