Immutability & Hashability
Hashable objects can live in sets and dict keys. Immutability is the usual way to guarantee hash stability - if an object's value changes after insertion, lookups break silently.
Recipe
key = (42, "ada") # tuple of hashables - OK
tags = frozenset(["a", "b"]) # hashable set
# NOT hashable:
# bad = ([1, 2],) # list inside tuple
# { [1, 2]: "x" } # list as keyWhen to reach for this:
- Using objects as dict keys or set members
- Building caches keyed by arguments
- Designing value objects in domain models
- Debugging
TypeError: unhashable type
Working Example
from dataclasses import dataclass
@dataclass(frozen=True)
class UserId:
value: int
def cache_key(*parts: object) -> tuple:
return tuple(parts)
def dedupe_records(records: list[tuple[str, int]]) -> list[tuple[str, int]]:
seen: set[tuple[str, int]] = set()
unique: list[tuple[str, int]] = []
for record in records:
if record not in seen:
seen.add(record)
unique.append(record)
return unique
class Point:
__slots__ = ("x", "y")
def __init__(self, x: int, y: int) -> None:
self.x = x
self.y = y
def __eq__(self, other: object) -> bool:
if not isinstance(other, Point):
return NotImplemented
return (self.x, self.y) == (other.x, other.y)
def __hash__(self) -> int:
return hash((self.x, self.y))
if __name__ == "__main__":
index: dict[UserId, str] = {UserId(1): "Ada"}
print(index[UserId(1)])
print(dedupe_records([("a", 1), ("a", 1), ("b", 2)]))What this demonstrates:
frozen=Truedataclass is hashable when fields are hashable- Tuple cache keys combine hashable parts
- Custom class implements matching
__eq__and__hash__ __slots__reduces memory for many small objects
Deep Dive
How It Works
__hash__- Integer digest used to locate bucket in hash table.__eq__- Equality must agree with hash: equal objects equal hash.- Immutable builtins -
int,str,bytes,tuple(if elements hashable),frozenset. - Mutable builtins -
list,dict,set,bytearray- no stable hash. - User classes - Default instance hash is by
idunless overridden.
Hashability Rules
| Object | Hashable? |
|---|---|
tuple of hashables | Yes |
tuple containing list | No |
frozenset | Yes |
set | No |
frozen dataclass | Yes if fields hashable |
Python Notes
# dataclasses with unsafe_hash generates __hash__ from fields
@dataclass(frozen=True)
class Coord:
x: int
y: int
# disable hash when mutable semantics needed
@dataclass(eq=True, frozen=False)
class Buffer:
data: bytearrayGotchas
- Mutable dataclass in set - Dataclass mutable → unhashable by default. Fix:
frozen=Trueor exclude from sets. - Custom eq without hash - Python sets
__hash__ = None. Fix: Implement consistent__hash__or usefunctools.cached_propertypatterns carefully. - Hash changes when fields change - Violates dict/set contract. Fix: Immutable objects only as keys.
- NaN floats -
float('nan')hashable butnan != nan. Fix: Avoid NaN keys. - Tuples with mutable lists inside - Tuple hashable but list content can mutate breaking invariants if exposed. Fix: Do not mutate nested mutables used in keys.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
id(obj) dict | Identity map for mutable objects | Value-based dedupe |
json.dumps key | Serializable dict keys | Need speed or non-JSON types |
hashlib digest | Large blob fingerprint | Need object equality semantics |
| DB unique index | Persistent uniqueness | In-memory-only pipeline |
FAQs
Why unhashable list?
Lists mutate in place - hash would become invalid after insertion into set/dict.
Are dicts ever hashable?
No. Use frozenset of items or tuple of pairs if you need hashable mapping-like key.
frozen dataclass always hashable?
Only if all fields hashable. Mutable field types block hashing.
Can I hash my custom class?
Yes with __hash__ and __eq__ consistent. Prefer frozen dataclass for records.
What is __hash__ = None?
Marks class explicitly unhashable - instances cannot be set/dict keys.
tuple vs frozenset key?
Tuple for ordered composite key. frozenset for unordered tag sets.
Does hash equal identity?
Default object hash may use id; value-based classes must implement value hash.
lru_cache hash?
Cache keys use hash of arguments - arguments must be hashable.
bytearray hashable?
No - mutable. Use bytes for hashable binary key.
Enum members hashable?
Yes - Enum members are singletons with stable identity and hash.
Related
- Dictionaries - key requirements
- Sets & Frozensets - frozenset usage
- dataclasses - frozen records
- Dunder / Magic Methods - eq and hash
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.