Python unhashable type list — ошибка ровно с одним смыслом: ты положил список туда, где Python требует хешируемый объект, то есть в ключ словаря или в элемент множества. Чинится она механически — tuple(row) вместо row: кортеж хешируется, потому что его нельзя изменить.
Полностью сообщение выглядит так: TypeError: unhashable type: 'list'. Тип в конце меняется — бывает 'dict', бывает 'set', — но причина всегда одна, и дальше разобраны все места, где на неё натыкаются.
Что означает ошибка unhashable type: 'list'?
Сообщение TypeError: unhashable type: 'list' означает, что Python попытался посчитать хеш от списка и отказался. Хеш нужен словарям и множествам: по нему они за одно действие находят нужную ячейку, вместо того чтобы перебирать всё подряд. Список хеш не поддерживает, поэтому любая попытка сделать его ключом словаря, элементом множества или левым операндом in для множества падает с этой ошибкой.
Минимальный пример — файл prices.py:
prices = {}
key = ["Москва", "хлеб"]
prices[key] = 45
Traceback (most recent call last):
File "prices.py", line 3, in <module>
prices[key] = 45
~~~~~~^^^^^
TypeError: unhashable type: 'list'
Значки ~~~~~~^^^^^ под кодом (Python 3.11 и новее рисует их сам; форма подчёркивания от версии к версии слегка меняется) показывают точное место: prices[key]. То есть виноват не список сам по себе, а попытка использовать его как ключ. Рабочая версия отличается одним словом:
prices = {}
key = ["Москва", "хлеб"]
prices[tuple(key)] = 45
print(prices)
{('Москва', 'хлеб'): 45}
Что такое хешируемый объект простыми словами?
Хешируемый объект — это объект, от которого можно посчитать hash(), и полученное число не меняется, пока объект жив. Словарь раскладывает ключи по «полкам» согласно этому числу и по нему же потом ищет. Если бы объект после укладки изменился, номер полки перестал бы совпадать и ключ было бы уже не найти. Поэтому неизменяемые объекты — числа, строки, кортежи, frozenset, None, True — хешируемые, а изменяемые — список, словарь, множество — нет.
print(hash(42))
print(hash((1, 2)) == hash((1, 2)))
print(list.__hash__ is None)
print(tuple.__hash__ is None)
42
True
True
False
list.__hash__ is None — не недоделка, а осознанный запрет: у типа list хеширование выключено явно. Что было бы без запрета, видно на классе, который считает хеш от изменяемого поля:
class City:
def __init__(self, name):
self.name = name
def __hash__(self):
return hash(self.name)
def __eq__(self, other):
return self.name == other.name
msk = City("Москва")
seen = {msk}
msk.name = "Казань"
print(msk in seen)
False
Объект лежит в множестве, но множество его больше не находит: хеш изменился, а «полка» осталась старой. Ровно от такого класса багов список и защищён запретом. Как правильно связывать __eq__ и __hash__ в своих классах, показано в справочнике — равенство и хеширование.
Почему список нельзя использовать как ключ словаря, а кортеж можно?
Кортеж можно, потому что его нельзя изменить: после создания ("Москва", "хлеб") элементы не заменить, не добавить и не удалить, значит, хеш посчитан раз и навсегда. Список же меняют append, remove, sort и присваивание по индексу — его хеш пришлось бы пересчитывать, а словарь об этом никогда бы не узнал. Отсюда практическое правило: составной ключ в Python — всегда кортеж.
| Объект |
Хешируемый |
Чем заменить |
[1, 2] — список |
нет |
(1, 2) — кортеж |
{"город": "Москва"} — словарь |
нет |
кортеж значений или json-строка |
{1, 2} — множество |
нет |
frozenset({1, 2}) |
("Москва", [1, 2]) — кортеж со списком |
нет |
("Москва", (1, 2)) |
(1, 2) — кортеж из неизменяемых |
да |
— |
"хлеб", 45, 3.5, True, None |
да |
— |
Кортеж как неизменяемая последовательность разобран отдельно: tuple в справочнике.
Три места, где эта ошибка появляется чаще всего
Три сценария дают почти все случаи: список в ключе словаря, дедупликация через set() и проверка вхождения в множество. Ещё две строки в таблице — родственные сообщения с тем же корнем: словарь и множество тоже изменяемы, поэтому в ключи тоже не годятся.
| Что ты делаешь |
Строка кода |
Сообщение |
| кладёшь список в ключ словаря |
prices[["Москва", "хлеб"]] = 45 |
unhashable type: 'list' |
| дедуплицируешь список списков |
set(rows) |
unhashable type: 'list' |
| ищешь список в множестве |
row in seen |
unhashable type: 'list' |
| кладёшь словарь в ключ словаря |
counts[{"город": "Москва"}] = 5 |
unhashable type: 'dict' |
| кладёшь множество в множество |
groups.add({"хлеб", "молоко"}) |
unhashable type: 'set' |
Отдельная неожиданность — dict.get(). Он выглядит как безопасное чтение со значением по умолчанию, но падает точно так же (файл prices.py):
prices = {}
prices.get(["Москва", "хлеб"], 0)
Traceback (most recent call last):
File "prices.py", line 2, in <module>
prices.get(["Москва", "хлеб"], 0)
TypeError: unhashable type: 'list'
Значение по умолчанию страхует от отсутствующего ключа, а не от нехешируемого: чтобы вообще понять, есть такой ключ или нет, словарю сначала нужен хеш.
Как убрать дубликаты из списка списков?
Преврати каждую вложенную строку в кортеж — и дедупликация заработает. Прямой set(rows) на списке списков падает всегда, потому что множество обязано хешировать каждый элемент, который в него кладут (файл dedup.py):
rows = [["хлеб", 45], ["молоко", 89], ["хлеб", 45]]
unique = set(rows)
Traceback (most recent call last):
File "dedup.py", line 2, in <module>
unique = set(rows)
^^^^^^^^^
TypeError: unhashable type: 'list'
Если порядок не важен, хватает одной строки со включением:
rows = [["хлеб", 45], ["молоко", 89], ["хлеб", 45]]
unique = {tuple(row) for row in rows}
print(sorted(unique))
[('молоко', 89), ('хлеб', 45)]
Если порядок важен и на выходе нужны именно списки, работает шаблон «множество увиденного»:
rows = [["хлеб", 45], ["молоко", 89], ["хлеб", 45]]
seen = set()
unique = []
for row in rows:
key = tuple(row)
if key not in seen:
seen.add(key)
unique.append(row)
print(unique)
[['хлеб', 45], ['молоко', 89]]
Тот же приём короче — через dict.fromkeys, который сохраняет порядок первого появления, и через Counter, если нужны ещё и количества:
from collections import Counter
rows = [["хлеб", 45], ["молоко", 89], ["хлеб", 45]]
print(list(dict.fromkeys(map(tuple, rows))))
print(Counter(tuple(row) for row in rows))
[('хлеб', 45), ('молоко', 89)]
Counter({('хлеб', 45): 2, ('молоко', 89): 1})
Чего делать не стоит — вычищать дубликаты, удаляя элементы прямо во время прохода по списку: там своя ловушка, из-за которой соседние дубликаты выживают через один. Механика разобрана в статье про удаление из списка в цикле.
Группировка по составному ключу: tuple вместо списка
Вторая по частоте причина — отчёт, где строки надо сложить по паре колонок. Ключом становится кортеж из нужных полей, и никакого списка в ключе не появляется:
orders = [
("Москва", "хлеб", 45),
("Москва", "молоко", 89),
("Казань", "хлеб", 42),
("Москва", "хлеб", 47),
]
totals = {}
for city, product, price in orders:
key = (city, product)
totals[key] = totals.get(key, 0) + price
print(totals)
{('Москва', 'хлеб'): 92, ('Москва', 'молоко'): 89, ('Казань', 'хлеб'): 42}
Если строки приходят списками — например, из CSV, — достаточно взять срез и превратить его в кортеж: key = tuple(row[:2]). А собрать не сумму, а список значений на каждую пару помогает defaultdict:
from collections import defaultdict
rows = [
["Москва", "хлеб", 45],
["Казань", "хлеб", 42],
["Москва", "хлеб", 47],
]
groups = defaultdict(list)
for city, product, price in rows:
groups[(city, product)].append(price)
print(dict(groups))
{('Москва', 'хлеб'): [45, 47], ('Казань', 'хлеб'): [42]}
Обрати внимание: значениями остаются обычные списки, и это законно. Требование хешируемости касается только ключей словаря и элементов множества — значения могут быть какими угодно изменяемыми. Потренироваться на такой группировке можно на задаче «Сложи одинаковые товары».
frozenset: когда ключом должно быть множество
frozenset — это неизменяемое множество, и оно хешируется. Он нужен, когда ключ по смыслу является набором без порядка: набор тегов, состав команды, комбинация опций. Два frozenset с одинаковым содержимым равны и дают одинаковый хеш, даже если элементы перечислены в разном порядке.
tags = {}
tags[frozenset({"python", "sql"})] = 12
tags[frozenset({"python", "docker"})] = 3
print(tags[frozenset({"sql", "python"})])
12
С кортежем так не выйдет: ("python", "sql") и ("sql", "python") — два разных ключа. Выбирай кортеж, когда порядок полей значим (город, товар), и frozenset, когда значим только состав. Готовый пример — frozenset как ключ словаря в справочнике.
Почему кортеж со списком внутри тоже unhashable?
Потому что неизменяемость кортежа — это обещание про ссылки, а не про объекты за ними. Кортеж гарантирует, что его ячейки будут указывать на те же самые объекты, но если один из этих объектов — список, его содержимое всё равно можно поменять. Хеш кортежа складывается из хешей элементов, и на списке подсчёт немедленно падает (файл keys.py):
key = ("Москва", ["хлеб", "молоко"])
print(hash(key))
Traceback (most recent call last):
File "keys.py", line 2, in <module>
print(hash(key))
^^^^^^^^^
TypeError: unhashable type: 'list'
Лечится «заморозкой» вглубь: ("Москва", ("хлеб", "молоко")). Если вложенность произвольной глубины, пиши рекурсивное преобразование или сериализуй структуру в строку — об этом ниже.
Что делать, если ключ обязан оставаться изменяемым?
Сам список изменяемым остаться может — в ключ уходит его снимок. tuple(row) копирует текущее содержимое, дальше список живёт своей жизнью, а ключ в словаре остаётся таким, каким был в момент записи. Это не побочный эффект, а именно то поведение, которое делает словарь предсказуемым.
key = ["Москва", "хлеб"]
prices = {tuple(key): 45}
key.append("2026")
print(prices)
print(("Москва", "хлеб") in prices)
{('Москва', 'хлеб'): 45}
True
Логика тут та же, что и с переменной цикла: изменение одного объекта не переписывает другой автоматически — про этот источник путаницы есть отдельный разбор, почему присваивание переменной цикла не меняет список.
Когда ключом по смыслу должен быть словарь, вариантов три:
| Ситуация |
Решение |
Почему работает |
| набор полей фиксирован |
tuple(d[k] for k in ("город", "товар")) |
быстрый и читаемый ключ |
| набор полей заранее неизвестен |
json.dumps(d, sort_keys=True, ensure_ascii=False) |
sort_keys убирает влияние порядка ключей |
| ключ по смыслу — набор пар |
frozenset(d.items()) |
годится, пока все значения хешируемы |
import json
records = [
{"город": "Москва", "товар": "хлеб"},
{"товар": "хлеб", "город": "Москва"},
{"город": "Казань", "товар": "хлеб"},
]
seen = set()
unique = []
for row in records:
key = json.dumps(row, sort_keys=True, ensure_ascii=False)
if key not in seen:
seen.add(key)
unique.append(row)
print(len(unique))
2
Первые два словаря отличаются только порядком ключей, и sort_keys=True схлопывает их в один ключ. Именно поэтому str(row) — плохая замена json.dumps: порядок он не нормализует. Второй аргумент, ensure_ascii=False, к хешированию отношения не имеет — он лишь оставляет русские буквы буквами вместо юникод-последовательностей; за что именно он отвечает и почему одного его мало при записи в файл, разобрано в статье JSON: русские буквы вместо текста коды.
Чем эта ошибка отличается от других TypeError со списками?
unhashable type: 'list' возникает, когда список используют как ключ словаря или элемент множества, а list indices must be integers or slices, not str — наоборот, когда к списку обращаются как к словарю, строкой в квадратных скобках. Первая говорит «список нельзя положить в ключ», вторая — «список нельзя индексировать ключом». Разбор второй, с типичным случаем JSON-ответа, лежит в статье про list indices must be integers.
Соседние сообщения полезно держать рядом: KeyError — ключ хешируемый, но его нет в словаре; IndexError — индекс за пределами списка; unhashable type — до поиска дело даже не дошло, Python отказался вычислять хеш.
Частые ошибки
- Кортеж только при записи, а при поиске снова список.
seen.add(tuple(row)), а потом if row in seen — и падение на проверке. Преобразование нужно с обеих сторон: if tuple(row) in seen.
str(row) вместо tuple(row). Для списков сработает, но для словарей str({"город": "Москва", "товар": "хлеб"}) и str({"товар": "хлеб", "город": "Москва"}) дают разные строки при равных словарях. Нужен json.dumps(..., sort_keys=True).
- Надежда на
dict.get(). Значение по умолчанию не спасает: prices.get(["Москва"], 0) падает так же, как prices[["Москва"]].
- Заморозка наполовину.
("Москва", ["хлеб", "молоко"]) по-прежнему нехешируем — вложенный список тоже надо превратить в кортеж.
- Попытка сделать неизменяемыми ещё и значения. Ограничение касается только ключей словаря и элементов множества; значением спокойно может быть список, как в примере с
defaultdict(list).
- Ключ-набор через кортеж. Если порядок элементов в ключе не важен,
tuple(sorted(...)) работает, но frozenset честнее: он прямо говорит, что порядок не важен.
Практика: закрепить в браузере
Механику ключей и множеств лучше всего закрепить руками, ничего не устанавливая. На тренажёре для этого подойдут задачи, которые целиком держатся на хешируемых ключах: «Сложи одинаковые товары» — группировка по ключу и суммирование значений, «Слово без повторов» — множество уже увиденных символов. Дальше можно взять весь бесплатный блок задач по словарям: там ровно те приёмы, которые убирают эту ошибку из кода навсегда.
Мини-резюме
TypeError: unhashable type: 'list' значит: список попал в ключ словаря, в множество или в проверку in по множеству.
- Для встроенных типов хешируемый = неизменяемый: числа, строки, кортежи и
frozenset можно, список, словарь и множество — нет.
- Универсальный фикс для составного ключа —
tuple(row), причём преобразовывать нужно и при записи, и при поиске.
- Дедупликация списка списков:
{tuple(row) for row in rows} или шаблон seen с сохранением порядка.
- Ключ-набор без порядка —
frozenset; ключ-словарь — json.dumps(..., sort_keys=True).
- Кортеж со списком внутри тоже нехешируем: неизменяемость кортежа не распространяется на его содержимое.
- Значения словаря могут быть любыми изменяемыми объектами — ограничение действует только на ключи.
Python unhashable type list — ошибка ровно с одним смыслом: ты положил список туда, где Python требует хешируемый объект, то есть в ключ словаря или в элемент множества. Чинится она механически —
tuple(row)вместоrow: кортеж хешируется, потому что его нельзя изменить.Полностью сообщение выглядит так:
TypeError: unhashable type: 'list'. Тип в конце меняется — бывает'dict', бывает'set', — но причина всегда одна, и дальше разобраны все места, где на неё натыкаются.Что означает ошибка unhashable type: 'list'?
Сообщение
TypeError: unhashable type: 'list'означает, что Python попытался посчитать хеш от списка и отказался. Хеш нужен словарям и множествам: по нему они за одно действие находят нужную ячейку, вместо того чтобы перебирать всё подряд. Список хеш не поддерживает, поэтому любая попытка сделать его ключом словаря, элементом множества или левым операндомinдля множества падает с этой ошибкой.Минимальный пример — файл
prices.py:prices = {} key = ["Москва", "хлеб"] prices[key] = 45Значки
~~~~~~^^^^^под кодом (Python 3.11 и новее рисует их сам; форма подчёркивания от версии к версии слегка меняется) показывают точное место:prices[key]. То есть виноват не список сам по себе, а попытка использовать его как ключ. Рабочая версия отличается одним словом:prices = {} key = ["Москва", "хлеб"] prices[tuple(key)] = 45 print(prices)Что такое хешируемый объект простыми словами?
Хешируемый объект — это объект, от которого можно посчитать
hash(), и полученное число не меняется, пока объект жив. Словарь раскладывает ключи по «полкам» согласно этому числу и по нему же потом ищет. Если бы объект после укладки изменился, номер полки перестал бы совпадать и ключ было бы уже не найти. Поэтому неизменяемые объекты — числа, строки, кортежи,frozenset,None,True— хешируемые, а изменяемые — список, словарь, множество — нет.print(hash(42)) print(hash((1, 2)) == hash((1, 2))) print(list.__hash__ is None) print(tuple.__hash__ is None)list.__hash__ is None— не недоделка, а осознанный запрет: у типаlistхеширование выключено явно. Что было бы без запрета, видно на классе, который считает хеш от изменяемого поля:class City: def __init__(self, name): self.name = name def __hash__(self): return hash(self.name) def __eq__(self, other): return self.name == other.name msk = City("Москва") seen = {msk} msk.name = "Казань" print(msk in seen)Объект лежит в множестве, но множество его больше не находит: хеш изменился, а «полка» осталась старой. Ровно от такого класса багов список и защищён запретом. Как правильно связывать
__eq__и__hash__в своих классах, показано в справочнике — равенство и хеширование.Почему список нельзя использовать как ключ словаря, а кортеж можно?
Кортеж можно, потому что его нельзя изменить: после создания
("Москва", "хлеб")элементы не заменить, не добавить и не удалить, значит, хеш посчитан раз и навсегда. Список же меняютappend,remove,sortи присваивание по индексу — его хеш пришлось бы пересчитывать, а словарь об этом никогда бы не узнал. Отсюда практическое правило: составной ключ в Python — всегда кортеж.[1, 2]— список(1, 2)— кортеж{"город": "Москва"}— словарь{1, 2}— множествоfrozenset({1, 2})("Москва", [1, 2])— кортеж со списком("Москва", (1, 2))(1, 2)— кортеж из неизменяемых"хлеб",45,3.5,True,NoneКортеж как неизменяемая последовательность разобран отдельно: tuple в справочнике.
Три места, где эта ошибка появляется чаще всего
Три сценария дают почти все случаи: список в ключе словаря, дедупликация через
set()и проверка вхождения в множество. Ещё две строки в таблице — родственные сообщения с тем же корнем: словарь и множество тоже изменяемы, поэтому в ключи тоже не годятся.prices[["Москва", "хлеб"]] = 45unhashable type: 'list'set(rows)unhashable type: 'list'row in seenunhashable type: 'list'counts[{"город": "Москва"}] = 5unhashable type: 'dict'groups.add({"хлеб", "молоко"})unhashable type: 'set'Отдельная неожиданность —
dict.get(). Он выглядит как безопасное чтение со значением по умолчанию, но падает точно так же (файлprices.py):prices = {} prices.get(["Москва", "хлеб"], 0)Значение по умолчанию страхует от отсутствующего ключа, а не от нехешируемого: чтобы вообще понять, есть такой ключ или нет, словарю сначала нужен хеш.
Как убрать дубликаты из списка списков?
Преврати каждую вложенную строку в кортеж — и дедупликация заработает. Прямой
set(rows)на списке списков падает всегда, потому что множество обязано хешировать каждый элемент, который в него кладут (файлdedup.py):rows = [["хлеб", 45], ["молоко", 89], ["хлеб", 45]] unique = set(rows)Если порядок не важен, хватает одной строки со включением:
rows = [["хлеб", 45], ["молоко", 89], ["хлеб", 45]] unique = {tuple(row) for row in rows} print(sorted(unique))Если порядок важен и на выходе нужны именно списки, работает шаблон «множество увиденного»:
rows = [["хлеб", 45], ["молоко", 89], ["хлеб", 45]] seen = set() unique = [] for row in rows: key = tuple(row) if key not in seen: seen.add(key) unique.append(row) print(unique)Тот же приём короче — через
dict.fromkeys, который сохраняет порядок первого появления, и черезCounter, если нужны ещё и количества:from collections import Counter rows = [["хлеб", 45], ["молоко", 89], ["хлеб", 45]] print(list(dict.fromkeys(map(tuple, rows)))) print(Counter(tuple(row) for row in rows))Чего делать не стоит — вычищать дубликаты, удаляя элементы прямо во время прохода по списку: там своя ловушка, из-за которой соседние дубликаты выживают через один. Механика разобрана в статье про удаление из списка в цикле.
Группировка по составному ключу: tuple вместо списка
Вторая по частоте причина — отчёт, где строки надо сложить по паре колонок. Ключом становится кортеж из нужных полей, и никакого списка в ключе не появляется:
orders = [ ("Москва", "хлеб", 45), ("Москва", "молоко", 89), ("Казань", "хлеб", 42), ("Москва", "хлеб", 47), ] totals = {} for city, product, price in orders: key = (city, product) totals[key] = totals.get(key, 0) + price print(totals)Если строки приходят списками — например, из CSV, — достаточно взять срез и превратить его в кортеж:
key = tuple(row[:2]). А собрать не сумму, а список значений на каждую пару помогаетdefaultdict:from collections import defaultdict rows = [ ["Москва", "хлеб", 45], ["Казань", "хлеб", 42], ["Москва", "хлеб", 47], ] groups = defaultdict(list) for city, product, price in rows: groups[(city, product)].append(price) print(dict(groups))Обрати внимание: значениями остаются обычные списки, и это законно. Требование хешируемости касается только ключей словаря и элементов множества — значения могут быть какими угодно изменяемыми. Потренироваться на такой группировке можно на задаче «Сложи одинаковые товары».
frozenset: когда ключом должно быть множество
frozenset— это неизменяемое множество, и оно хешируется. Он нужен, когда ключ по смыслу является набором без порядка: набор тегов, состав команды, комбинация опций. Дваfrozensetс одинаковым содержимым равны и дают одинаковый хеш, даже если элементы перечислены в разном порядке.tags = {} tags[frozenset({"python", "sql"})] = 12 tags[frozenset({"python", "docker"})] = 3 print(tags[frozenset({"sql", "python"})])С кортежем так не выйдет:
("python", "sql")и("sql", "python")— два разных ключа. Выбирай кортеж, когда порядок полей значим (город, товар), иfrozenset, когда значим только состав. Готовый пример — frozenset как ключ словаря в справочнике.Почему кортеж со списком внутри тоже unhashable?
Потому что неизменяемость кортежа — это обещание про ссылки, а не про объекты за ними. Кортеж гарантирует, что его ячейки будут указывать на те же самые объекты, но если один из этих объектов — список, его содержимое всё равно можно поменять. Хеш кортежа складывается из хешей элементов, и на списке подсчёт немедленно падает (файл
keys.py):key = ("Москва", ["хлеб", "молоко"]) print(hash(key))Лечится «заморозкой» вглубь:
("Москва", ("хлеб", "молоко")). Если вложенность произвольной глубины, пиши рекурсивное преобразование или сериализуй структуру в строку — об этом ниже.Что делать, если ключ обязан оставаться изменяемым?
Сам список изменяемым остаться может — в ключ уходит его снимок.
tuple(row)копирует текущее содержимое, дальше список живёт своей жизнью, а ключ в словаре остаётся таким, каким был в момент записи. Это не побочный эффект, а именно то поведение, которое делает словарь предсказуемым.key = ["Москва", "хлеб"] prices = {tuple(key): 45} key.append("2026") print(prices) print(("Москва", "хлеб") in prices)Логика тут та же, что и с переменной цикла: изменение одного объекта не переписывает другой автоматически — про этот источник путаницы есть отдельный разбор, почему присваивание переменной цикла не меняет список.
Когда ключом по смыслу должен быть словарь, вариантов три:
tuple(d[k] for k in ("город", "товар"))json.dumps(d, sort_keys=True, ensure_ascii=False)sort_keysубирает влияние порядка ключейfrozenset(d.items())import json records = [ {"город": "Москва", "товар": "хлеб"}, {"товар": "хлеб", "город": "Москва"}, {"город": "Казань", "товар": "хлеб"}, ] seen = set() unique = [] for row in records: key = json.dumps(row, sort_keys=True, ensure_ascii=False) if key not in seen: seen.add(key) unique.append(row) print(len(unique))Первые два словаря отличаются только порядком ключей, и
sort_keys=Trueсхлопывает их в один ключ. Именно поэтомуstr(row)— плохая заменаjson.dumps: порядок он не нормализует. Второй аргумент,ensure_ascii=False, к хешированию отношения не имеет — он лишь оставляет русские буквы буквами вместо юникод-последовательностей; за что именно он отвечает и почему одного его мало при записи в файл, разобрано в статье JSON: русские буквы вместо текста коды.Чем эта ошибка отличается от других TypeError со списками?
unhashable type: 'list'возникает, когда список используют как ключ словаря или элемент множества, аlist indices must be integers or slices, not str— наоборот, когда к списку обращаются как к словарю, строкой в квадратных скобках. Первая говорит «список нельзя положить в ключ», вторая — «список нельзя индексировать ключом». Разбор второй, с типичным случаем JSON-ответа, лежит в статье про list indices must be integers.Соседние сообщения полезно держать рядом:
KeyError— ключ хешируемый, но его нет в словаре;IndexError— индекс за пределами списка;unhashable type— до поиска дело даже не дошло, Python отказался вычислять хеш.Частые ошибки
seen.add(tuple(row)), а потомif row in seen— и падение на проверке. Преобразование нужно с обеих сторон:if tuple(row) in seen.str(row)вместоtuple(row). Для списков сработает, но для словарейstr({"город": "Москва", "товар": "хлеб"})иstr({"товар": "хлеб", "город": "Москва"})дают разные строки при равных словарях. Нуженjson.dumps(..., sort_keys=True).dict.get(). Значение по умолчанию не спасает:prices.get(["Москва"], 0)падает так же, какprices[["Москва"]].("Москва", ["хлеб", "молоко"])по-прежнему нехешируем — вложенный список тоже надо превратить в кортеж.defaultdict(list).tuple(sorted(...))работает, ноfrozensetчестнее: он прямо говорит, что порядок не важен.Практика: закрепить в браузере
Механику ключей и множеств лучше всего закрепить руками, ничего не устанавливая. На тренажёре для этого подойдут задачи, которые целиком держатся на хешируемых ключах: «Сложи одинаковые товары» — группировка по ключу и суммирование значений, «Слово без повторов» — множество уже увиденных символов. Дальше можно взять весь бесплатный блок задач по словарям: там ровно те приёмы, которые убирают эту ошибку из кода навсегда.
Мини-резюме
TypeError: unhashable type: 'list'значит: список попал в ключ словаря, в множество или в проверкуinпо множеству.frozensetможно, список, словарь и множество — нет.tuple(row), причём преобразовывать нужно и при записи, и при поиске.{tuple(row) for row in rows}или шаблонseenс сохранением порядка.frozenset; ключ-словарь —json.dumps(..., sort_keys=True).