Почему кортеж с изменяемым элементом не может быть ключом в словаре Python
Что проверяют
Понимаете ли вы, что ключом объект делает не тип, а хешируемость, и как связаны __hash__ и __eq__. Уточняющий вопрос почти всегда один: в какой момент возникает ошибка, при создании кортежа или при вставке в словарь.
Как отвечать
Словарь ищет значение в два шага: по hash(key) вычисляет позицию в таблице, потом сравнивает кандидатов через ==. Значит хеш ключа обязан быть постоянным, а равные объекты обязаны давать равный хеш. Кортеж содержимое не копирует, его хеш считается рекурсивно по хешам элементов. Список нехешируем, у него __hash__ равен None, поэтому hash((1, [2, 3])) падает с TypeError: unhashable type: 'list'.
- когда именно падает: кортеж
(1, [2, 3])создаётся спокойно, ошибка возникает при хешировании: наd[key] = v, наkey in dили при добавлении в множество; - что было бы, работай это: положили ключ, изменили список внутри, хеш стал другим, а запись осталась в старой ячейке. Значение недостижимо, и
inначинает врать; - как чинить: приводить вложенное к неизменяемому. Список в кортеж, множество в
frozenset, словарь вtuple(sorted(d.items())), если его содержимое тоже хешируемо; - свои классы:
@dataclass(frozen=True)получает__hash__автоматически, а обычный@dataclassнет, потому что приeq=TruePython выставляет__hash__ = None. То же правило работает и для написанного руками__eq__без__hash__; - важная поправка: неизменяемость и хешируемость это не синонимы. Экземпляр обычного класса изменяем, но хешируем, потому что базовый
__hash__производный от адреса объекта. И наоборот,float('nan')хешируется, но не равен сам себе, и достать такой ключ можно только по той же ссылке.
Где спотыкаются
Говорят «ключ должен быть неизменяемым» и на этом останавливаются. Правильная формулировка идёт через хешируемость, иначе не объяснить ни свои классы, ни frozenset.
Утверждают, что ошибка вылетит при создании кортежа. Проверяется в интерпретаторе за десять секунд и выдаёт заученный ответ.
Предлагают подменить ключ на str(key) или id(key). Строковое представление склеивает разные объекты и ломается на вложенности, а id переиспользуется после сборки мусора.