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.
Search across all documentation pages
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.
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:
TypeError: unhashable typefrom 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=True dataclass is hashable when fields are hashable__eq__ and __hash____slots__ reduces memory for many small objects__hash__ - Integer digest used to locate bucket in hash table.__eq__ - Equality must agree with hash: equal objects equal hash.int, str, bytes, tuple (if elements hashable), frozenset.list, dict, set, bytearray - no stable hash.id unless overridden.| Object | Hashable? |
|---|---|
tuple of hashables | Yes |
tuple containing list | No |
frozenset | Yes |
set | No |
frozen dataclass | Yes if fields hashable |
# 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: bytearrayfrozen=True or exclude from sets.__hash__ = None. Fix: Implement consistent __hash__ or use functools.cached_property patterns carefully.float('nan') hashable but nan != nan. Fix: Avoid NaN keys.| 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 |
Lists mutate in place - hash would become invalid after insertion into set/dict.
No. Use frozenset of items or tuple of pairs if you need hashable mapping-like key.
Only if all fields hashable. Mutable field types block hashing.
Yes with __hash__ and __eq__ consistent. Prefer frozen dataclass for records.
Marks class explicitly unhashable - instances cannot be set/dict keys.
Tuple for ordered composite key. frozenset for unordered tag sets.
Default object hash may use id; value-based classes must implement value hash.
Cache keys use hash of arguments - arguments must be hashable.
No - mutable. Use bytes for hashable binary key.
Yes - Enum members are singletons with stable identity 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+.
Reviewed by Chris St. John·Last updated Jul 16, 2026