Equality and hashing
Design stable value objects for dictionaries and sets
Identity and value
is checks whether references identify the same object; == compares values. Two lists can be equal but distinct. Use is None for absence and == for ordinary values. Do not depend on reuse of small integers or strings.
Classes normally inherit identity-based equality. Define __eq__ to compare domain values. Return NotImplemented for an unsupported operand type so Python can try the other operand's comparison.
Hash-based lookup
Dictionaries and sets use hashes to locate candidate entries, then equality to check matches. Equal objects must have equal hashes; unequal objects may collide. A key's hash must remain stable while stored.
from dataclasses import dataclass
@dataclass(frozen=True)
class Coordinate:
row: int
column: int
seen = {Coordinate(2, 3), Coordinate(2, 3), Coordinate(3, 2)}
print(len(seen)) # 2The coordinate fields define value equality. Their frozen integer values support stable generated hashing, so equal coordinates collapse into one set entry.
Normalize identity fields once at construction, then use that same representation for equality and hashing. A tuple may suffice for a small composite key; a named class can hold normalization and a clearer schema.
Comparison dispatch
NotImplemented is a dispatch result, not False or an exception:
class Opaque:
def __eq__(self, other):
return NotImplemented
class AcceptsOpaque:
def __eq__(self, other):
return isinstance(other, Opaque)
left = Opaque()
right = AcceptsOpaque()
print(left == right, right == left) # True True
print(left == object()) # FalseWhen both operands decline equality, == falls back to identity. Defining __eq__ without a suitable __hash__ makes a class unhashable by default. The example classes above are therefore not value keys. NotImplementedError instead indicates an unfinished or unsupported operation.
Gotchas
- Mutating identity fields after insertion can break lookup even while the key remains stored. Use immutable identity fields or identity-based semantics for mutable entities.
- A hash is not a persistent ID or cryptographic digest. Some built-in hashes vary between processes; matching hashes do not establish equality.
- Test lookup with equal, separately constructed keys and an unrelated operand. Do not assert a particular hash integer.
Put it into practice
Implement the exercise in a full-screen workspace, then run its tests.