Python Hashable Objects: Implementation Guide
python hashable objects: Understand what makes Python objects hashable, how to implement __hash__ and __eq__, and avoid common pitfalls with dict and set usage.
python hashable objects requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
What Makes an Object Hashable?
An object is hashable when Python can compute a stable integer value for it using the __hash__ method. That integer is used by dictionaries and sets to place the object in a bucket. For the data structure to work correctly, the hash value must never change while the object is used as a key or set member. In practice, this means hashable objects are usually immutable, or at least their hash-relevant attributes are immutable.
Built-in immutable types such as int, str, tuple, and frozenset are hashable. Mutable types like list, dict, and set are not, because their contents can change and therefore their hash would change if it were computed from the contents.
The Relationship Between hash and eq
Python enforces a contract between __hash__ and __eq__: if two objects compare equal, their hash values must be equal. If you define __eq__ in a class without also defining __hash__, Python sets __hash__ to None, making instances unhashable. This prevents you from accidentally violating the contract.
When you implement both methods, you must keep them consistent. For example, if equality is based on an id attribute, then the hash must also be derived from that same attribute. Using different attributes for equality and hashing will break dictionary and set behavior in subtle ways.
Implementing hash for Custom Classes
For a simple value object, the safest approach is to compute the hash from a tuple of the same attributes used in __eq__. Here is a minimal Point class:
class Point: def __init__(self, x, y): self.x = x self.y = y def __eq__(self, other): if not isinstance(other, Point): return NotImplemented return (self.x, self.y) == (other.x, other.y) def __hash__(self): return hash((self.x, self.y))
The tuple of attributes is immutable, and hash() on a tuple combines the hashes of its elements consistently. This keeps __hash__ and __eq__ aligned.
If you use @dataclass(frozen=True), Python generates both __eq__ and __hash__ for you, based on the fields. Frozen dataclasses are immutable, so they are safe to use as dictionary keys.
Common Mistakes with Hashable Objects
The most frequent error is making a mutable object hashable. If you insert an object into a set and then mutate one of its attributes, the object's hash changes. The set still stores it in the old bucket, so lookups fail or return incorrect results. This is why mutable objects should never be hashable.
Another mistake is defining __eq__ without __hash__. As mentioned, this makes the class unhashable. If you need equality but not hashing, you can set __hash__ = None explicitly, but then you cannot use instances as dict keys.
A third issue is using a mutable attribute inside __hash__. Even if the object itself is not mutated, if the attribute is a list, its hash will change when the list changes, breaking the invariant.
Hashable vs Unhashable Built-in Types
The table below summarizes common built-in types:
| Type | Hashable | Reason |
|---|---|---|
| int | Yes | Immutable, hash built-in |
| float | Yes | Immutable, hash built-in |
| str | Yes | Immutable, hash built-in |
| tuple | Yes | Immutable if elements are hashable |
| frozenset | Yes | Immutable, hash built-in |
| bytes | Yes | Immutable, hash built-in |
| list | No | Mutable |
| dict | No | Mutable |
| set | No | Mutable |
| bytearray | No | Mutable |
A tuple is hashable only if all of its elements are hashable. A tuple containing a list is unhashable.
Performance and Memory Considerations
Computing a hash has a cost. For built-in types, Python uses optimized C implementations. For custom classes, the __hash__ method runs in Python, so it is slower. Using a tuple of attributes is usually fast enough for most applications, but if you have a class with many fields, consider whether you need all of them in the hash.
Hash collisions are another factor. Python's dict and set use open addressing, so collisions reduce lookup performance. A good hash function spreads values evenly. The default hash() for integers and strings is well-distributed. For custom classes, using a tuple of attributes often produces a reasonable distribution, but you should avoid overly simple hashes like returning a constant.
If you need to store many objects in a set and performance matters, measure the actual behavior. In most cases, the overhead of __hash__ is negligible compared to the overall operation.
When to Use Custom Hashable Objects
Use custom hashable objects when you need value-based equality and want to use instances as dictionary keys or set members. For example, a Point class that represents coordinates is a natural candidate. If the object should be mutable, you cannot safely use it as a key. In that case, use a separate immutable key, such as a tuple of the mutable object's identifying attributes.
For simple data containers, prefer @dataclass(frozen=True) over manual implementations. It generates correct __hash__ and __eq__ methods and keeps the code concise. If you need a custom hash for a specific reason, such as ignoring certain fields, implement both methods explicitly and test that equal objects produce equal hashes.
Remember that hashability is a design decision. Not every class needs to be hashable. Only implement it when you have a concrete need for dict or set usage.