Home/Python/Equality and hashing

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.

Python
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))  # 2

The 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:

Python
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())  # False

When 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.

Open practice
Next lessonProperties and descriptors