普通dataclass的hash为False,因为Python默认生成的__hash__为None;即使设hash=True,含可变字段(如list)时也会被静默忽略,因哈希值需在对象生命周期内恒定。

为什么普通 dataclass 的 hash 是 False
Python 默认给 @dataclass 生成的 __hash__ 方法是 None,哪怕你显式写 hash=True,只要类里有可变字段(比如 list、dict),解释器就会静默忽略它,最终实例仍是不可哈希的。根本原因在于:Python 要求哈希值在对象生命周期内恒定,而可变字段的内容随时可能变。
frozen=True 是必要条件,但还不够
frozen=True 会让 dataclass 把所有字段设为只读(背后调用 object.__setattr__ 拦截赋值),这是启用 hash 的前提。但仅加 frozen=True 不等于自动有 hash —— 你必须**同时指定 hash=None 或明确写 hash=True**,否则 Python 仍按默认逻辑推导(即:有可变字段 → 禁用 hash)。
- ✅ 正确:
@dataclass(frozen=True, hash=True) - ✅ 也行:
@dataclass(frozen=True, hash=None)(此时会按字段类型自动推导 hash 行为) - ❌ 错误:
@dataclass(frozen=True)(hash未显式设置,且字段含list等 →__hash__ = None)
字段类型决定 hash 是否真能用
即使加了 frozen=True 和 hash=True,如果某个字段本身不可哈希(例如 list、dict、自定义类没实现 __hash__),实例创建时不会报错,但调用 hash() 会立刻抛 TypeError: unhashable type。
常见修复方式:
- 把
list改成tuple(tuple可哈希,前提是元素也都可哈希) - 把
dict改成frozenset或tuple(sorted(dict.items())) - 对自定义类,确保它有确定的
__hash__且不依赖可变状态
示例:
@dataclass(frozen=True, hash=True)
class Point:
x: int
y: int
tags: tuple[str, ...] # ✅ 可哈希
❌ 这样会 runtime 报错:hash(Point(1, 2, ["a"])) → TypeError
tags: list[str]
嵌套 dataclass 的 hash 传递性
如果 frozen dataclass 的某个字段是另一个 frozen dataclass 实例,那它的 hash 值会被递归纳入计算 —— 但前提是那个嵌套类也满足 frozen=True 且所有字段可哈希。一旦其中任意一层出现不可哈希字段,整个链路就失效。
容易忽略的点:
-
field(default_factory=list)即使在 frozen 类里也不行 ——default_factory返回的仍是可变对象 -
InitVar字段不参与 hash 计算,但若它被用来初始化一个不可哈希的字段,问题照旧 - 使用
field(compare=False)的字段仍参与 hash,除非你也设hash=False
真正安全的初始化写法是:所有字段类型本身可哈希,且不依赖运行时动态构造的可变容器。










