eq defines equality; hash enables dict/set usage and must match equality
The contract is simple and unforgiving: if a == b then hash(a) == hash(b). Dicts and sets rely on hashing to find a bucket and then use equality to confirm the match. If you define eq without hash, Python sets hash to None and the class becomes unhashable, which breaks dict keys, sets, and any caching keyed on the object. Conversely, if you define hash but not eq, two objects with the same hash but different identity are treated as distinct, which surprises people. The correct pattern is to derive the hash from the same fields that eq compares, and to keep those fields immutable for the lifetime of the object while it is in a hash-based container.
Default object identity: eq is is-like, hash is based on id. That is correct until you override equality.
Rule: equal objects must have equal hashes; unequal objects may share a hash but it degrades performance.
Mutable objects that participate in equality should generally not be hashable, or should not be used as dict keys.
Trade-off: value-based equality is convenient but makes objects unsuitable as identity-keyed dict keys. Choose deliberately.
Common mistake: overriding eq in a dataclass with eq=True and forgetting that frozen=True is what keeps it hashable by default.
Common mistake: including mutable fields in hash, which corrupts lookups after mutation.
Version note: dataclasses generate eq and, with frozen=True, also hash. Python 3.7+ behavior is stable.
0-2 years experience
2-5 years experience
5-8 years experience
8+ years experience