Getting __hash__ wrong breaks sets and dicts in silent, hard-to-debug ways.
Python requires that objects which compare equal have equal hashes, and that a hash never changes while the object is in a set or dict. Dataclasses follow these rules from the eq and frozen options:
eq=True, frozen=True:__hash__is generated from the fields (those withcompare=True, adjustable viafield(hash=...)).eq=True, frozen=False(the default):__hash__is set to None, so instances are unhashable. Mutable values with value-equality can't safely be hashed: change a field and the object is lost in its set.eq=False:__hash__is left alone, so the identity hash fromobject(or a parent) is used.- If you define
__hash__explicitly in the class body, it's kept unchanged.
unsafe_hash=True forces a field-based __hash__ on a mutable class. It's for cases where you promise not to mutate instances after hashing them, such as "logically immutable" objects that can't be frozen for performance or library reasons. If the promise is broken, lookups silently fail.
Frozen-ness is about instance attributes; a hash over fields that contain lists raises TypeError: unhashable type: 'list' at hash time. For dict keys, prefer frozen dataclasses with immutable field types (tuples, frozensets, other frozen dataclasses).
Example
from dataclasses import dataclass
@dataclass
class Mutable: x: int
@dataclass(frozen=True)
class Frozen: x: int
@dataclass(unsafe_hash=True)
class Risky: x: int
print(Mutable.__hash__) # None -> unhashable
print(hash(Frozen(1)) == hash(Frozen(1))) # True
r = Risky(1)
bucket = {r}
r.x = 2 # mutate after hashing...
print(r in bucket, Risky(2) in bucket) # False False -> object lost in its own set
@dataclass(frozen=True)
class WithList: items: list
try:
hash(WithList([1]))
except TypeError as e:
print(e) # unhashable type: 'list'Remember
- eq + frozen generates __hash__; eq without frozen makes instances unhashable
- eq=False keeps identity hashing; an explicit __hash__ is respected
- unsafe_hash=True is only safe if instances never change after hashing
Watch out for
- Using unsafe_hash=True on objects that are later mutated while in sets or dict keys.