PythonMastery
reference 4 min read · lesson 45 of 60 in Errors

TypeError: unhashable type: 'list'

1 · The lesson

read

What this error means

You tried to use a list as a dictionary key or as a member of a set. Both are built on hashing: Python turns the key into a number once and uses that number to find it again later. A list can change after you've stored it. If you appended one item, its hash would change, and the dictionary could no longer find it. So Python refuses to hash lists at all. The same rule covers dict and set, with the same message and a different type name.

When you see it

text
Traceback (most recent call last):
  File "dedupe.py", line 5, in <module>
    seen.add(row)
TypeError: unhashable type: 'list'

Why it happens

A mutable value was used where Python needs a value that can't change:

1. A set of lists. Deduplicating rows with set(rows) or seen.add(row), when each row is a list, which is exactly what csv.reader gives you.
2. A list as a dict key. Counting or grouping by a pair of values stored as [city, year].
3. A list hiding inside a tuple. (1, [2, 3]) is a tuple, but it's only hashable if everything inside it is.
4. The dict version. set_of_records.add({"id": 7}) fails the same way, with unhashable type: 'dict'.

python
rows = [["Oslo", 2024], ["Lima", 2024], ["Oslo", 2024]]
unique = set(rows)       # TypeError: unhashable type: 'list'

How to fix it

Turn the list into a tuple. A tuple can't change, so it can be hashed. For a row of fixed values, a tuple is also the more accurate type:

python
rows = [["Oslo", 2024], ["Lima", 2024], ["Oslo", 2024]]
unique = {tuple(row) for row in rows}
print(unique)            # {('Oslo', 2024), ('Lima', 2024)}

Need to keep the original order while removing duplicates? A set won't keep the order, but a dictionary keyed by tuples will:

python
unique_rows = list(dict.fromkeys(tuple(row) for row in rows))
# [('Oslo', 2024), ('Lima', 2024)]  (the first appearance wins)
+ setup added so this can run · re-runs 2 earlier blocks on this page first
rows = ["alpha", "beta", "gamma"]

# Earlier blocks on this page that this one builds on, re-run quietly first.
_pm_earlier = [
    'rows = [["Oslo", 2024], ["Lima", 2024], ["Oslo", 2024]]\nunique = set(rows)       # TypeError: unhashable type: \'list\'',
    'rows = [["Oslo", 2024], ["Lima", 2024], ["Oslo", 2024]]\nunique = {tuple(row) for row in rows}\nprint(unique)            # {(\'Oslo\', 2024), (\'Lima\', 2024)}',
]
def _pm_run_earlier(_srcs, _g):
    import sys as _s, io as _io, warnings as _w
    for _src in _srcs:
        _o, _e = _s.stdout, _s.stderr
        _s.stdout = _s.stderr = _io.StringIO()
        try:
            with _w.catch_warnings():
                _w.simplefilter('ignore')
                exec(compile(_src, '<earlier block>', 'exec'), _g)
        except (Exception, SystemExit):
            pass
        finally:
            _s.stdout, _s.stderr = _o, _e
    if 'matplotlib.pyplot' in _s.modules:
        _s.modules['matplotlib.pyplot'].close('all')   # their plots belong to their own blocks
_pm_run_earlier(_pm_earlier, globals())
del _pm_earlier, _pm_run_earlier

Counting by a combination of values? Key by a tuple.

python
from collections import Counter

visits = [["Oslo", 2024], ["Lima", 2024], ["Oslo", 2024]]
by_city_year = Counter(tuple(v) for v in visits)
print(by_city_year[("Oslo", 2024)])   # 2

Order of the items doesn't matter? Use frozenset. Then {"python", "sql"} and {"sql", "python"} become the same key:

python
skills_seen = {frozenset(["python", "sql"])}
print(frozenset(["sql", "python"]) in skills_seen)   # True

A dict as the key? Use its identifying field instead (records_by_id[record["id"]]), and don't try to hash the whole dict.

When you'd see this in real code

  • Removing duplicate rows from a CSV with set(csv.reader(f)).
  • A cache decorator, functools.lru_cache, wrapped around a function you call with a list argument. The cache uses the arguments as dictionary keys.
  • pandas df.groupby(...) or df.drop_duplicates() on a column whose cells hold lists.
  • Building a graph as edges = {[a, b]: weight} instead of {(a, b): weight}.

See Also

  • All Python errors: the full index, by type and by when it happens.
  • Tuples: the immutable sequence, and why that makes it a good key.
  • Sets: what can go in one, and why.
  • Dictionaries: keys, hashing and lookups.

Practice this

on practicepython.in

Short exercises that run in your browser and tell you what your code did, as well as whether a test passed.

Found this useful? Share it with a friend who’s learning Python.