普通dataclass的hash为False,因为Python默认生成的__hash__为None;即使设hash=True,含可变字段(如list)时也会被静默忽略,因哈希值需在对象生命周期内恒定。
Python 默认给 @dataclass 生成的 __hash__ 方法是 None,哪怕你显式写 hash=True,只要类里有可变字段(比如 list、dict),解释器就会静默忽略它,最终实例仍是不可哈希的。根本原因在于:Python 要求哈希值在对象生命周期内恒定,而可变字段的内容随时可能变。
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)即使加了 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]
如果 frozen dataclass 的某个字段是另一个 frozen dataclass 实例,那它的 hash 值会被递归纳入计算 —— 但前提是那个嵌套类也满足 frozen=True 且所有字段可哈希。一旦其中任意一层出现不可哈希字段,整个链路就失效。
容易忽略的点:
field(default_factory=list) 即使在 frozen 类里也不行 —— default_factory 返回的仍是可变对象InitVar 字段不参与 hash 计算,但若它被用来初始化一个不可哈希的字段,问题照旧field(compare=False) 的字段仍参与 hash,除非你也设 hash=False
