TypeError: unhashable type: 'dict' (tested fix)
TypeError: unhashable type: 'dict' means a dict was used as a set member, a dict key, or a cached function argument. Dicts are mutable, so they cannot be hashed. We reproduced each case and tested the fixes on Python 3.13.
1. Cause: a dict used where a hashable value is needed
rows = [{"id": 1, "tag": "a"}, {"id": 1, "tag": "a"}, {"id": 2, "tag": "b"}]
for label, fn in [("set(rows)", lambda: set(rows)),
("{row: 1}", lambda: {rows[0]: 1}),
("row in set()", lambda: rows[0] in set()),
("dict.fromkeys(rows)", lambda: dict.fromkeys(rows))]:
try:
fn()
except TypeError as e:
print(f"{label:20} -> TypeError: {e}")
print("row in list:", rows[0] in rows) # lists compare with ==, no hashing
Output:
set(rows) -> TypeError: unhashable type: 'dict'
{row: 1} -> TypeError: unhashable type: 'dict'
row in set() -> TypeError: unhashable type: 'dict'
dict.fromkeys(rows) -> TypeError: unhashable type: 'dict'
row in list: True
Set members, dict keys and x in some_set lookups all hash the value, and a dict is mutable, so it has no hash. The usual trigger is a list of dicts from JSON or a database (an API response, csv.DictReader rows) passed to set() to deduplicate it. Membership in a list works because lists compare with == and never hash.
2. Fix: deduplicate on a hashable key
import json
rows = [{"id": 1, "tag": "a"}, {"tag": "a", "id": 1}, {"id": 2, "tag": "b"}]
# Fix 1: dedupe on a hashable key built from the dict, keeping order
seen, unique = set(), []
for r in rows:
k = tuple(sorted(r.items()))
if k not in seen:
seen.add(k); unique.append(r)
print(unique)
# Fix 2: nested values (lists/dicts inside) -> use a canonical JSON string
nested = [{"id": 1, "tags": ["x"]}, {"tags": ["x"], "id": 1}]
print(len({json.dumps(r, sort_keys=True) for r in nested}))
try:
{tuple(sorted(r.items())) for r in nested}
except TypeError as e:
print("tuple(items) with a list inside -> TypeError:", e)
Output:
[{'id': 1, 'tag': 'a'}, {'id': 2, 'tag': 'b'}]
1
tuple(items) with a list inside -> TypeError: unhashable type: 'list'
tuple(sorted(d.items())) is hashable and ignores key order, so the first two rows count as the same row. It only works when every value is itself hashable; with a list or dict inside, it fails with unhashable type: 'list'. For nested data, json.dumps(d, sort_keys=True) gives a canonical string key.
3. lru_cache and other caches with dict arguments
from functools import lru_cache
@lru_cache
def price(opts):
return len(opts)
try:
price({"size": "L"})
except TypeError as e:
print("lru_cache ->", f"TypeError: {e}")
print("frozenset fix:", price(frozenset({"size": "L"}.items())))
Output:
lru_cache -> TypeError: unhashable type: 'dict'
frozenset fix: 1
functools.lru_cache builds its cache key by hashing the arguments, so passing a dict raises the same error. Pass a frozenset(d.items()) or a tuple instead (and rebuild the dict inside the function if needed).
Related: unhashable type: 'list' (all causes) ยท KeyError.
Frequently asked questions
Why is a dict unhashable in Python?
Dicts are mutable, so their hash could change; Python refuses to hash them. Sets, dict keys and lru_cache all need hashable values.
How do I remove duplicate dicts from a list?
Track a hashable key such as tuple(sorted(d.items())), or json.dumps(d, sort_keys=True) for nested values, in a set while you loop.
How do I use a dict with lru_cache?
Pass frozenset(d.items()) or a tuple instead of the dict; lru_cache hashes every argument.
Written with AI assistance. Measured on 2026-10-23 with Python 3.13.5 on Debian: set(), dict keys, set membership and dict.fromkeys with dicts, two dedupe fixes, and functools.lru_cache with a dict argument. Every block was run verbatim and its output pasted unedited. PlainHeaders is our extension; Python is free.