Будет ли кортеж хэшируемым, если содержит список
Что проверяют
Понимаете ли вы, откуда в Python берётся хэшируемость и что неизменяемость кортежа поверхностная. Следом часто просят объяснить, что произойдёт при t[1] += [4], и как сделать хэшируемый аналог.
Как отвечать
Короткий ответ: нет. Сам кортеж (1, [2, 3]) создаётся без ошибок, но hash() от него, использование как ключа dict или элемента set падает с TypeError: unhashable type: 'list'.
Почему так:
- хэш кортежа составной:
tuple.__hash__вычисляет хэши всех элементов и комбинирует их. Если хоть один элемент нехэшируемый, падает весь расчёт; - у списка нет хэша: в
listатрибут__hash__равенNone, потому что изменяемый объект мог бы поменять хэш после попадания в словарь и потеряться в нём; - неизменяемость поверхностная: кортеж не даёт заменить свои элементы, но список внутри меняется свободно,
t[1].append(4)работает; - проверка происходит в момент хэширования, а не при создании. Поэтому
isinstance(t, collections.abc.Hashable)вернётTrueдаже для кортежа со списком: у типаtupleметод__hash__есть.
Как сделать хэшируемым: заменить список на tuple или на frozenset, если порядок не важен. Для своих структур подходят @dataclass(frozen=True) и NamedTuple, но только с хэшируемыми полями. Полезно добавить правило про пользовательские классы: по умолчанию они хэшируются по идентичности, но если определить __eq__ без __hash__, Python выставит __hash__ = None, и объект станет нехэшируемым.
Где спотыкаются
Отвечают «кортеж неизменяемый, значит всегда хэшируемый». Хэшируемость зависит от содержимого, а не только от типа.
Не знают фокус с t[1] += [4]: Python бросит TypeError, но список внутри кортежа к этому моменту уже изменится, потому что += сначала выполняет list.__iadd__, а падает только на присваивании элемента кортежа.
Считают, что frozenset решает любую проблему. Его элементы тоже обязаны быть хэшируемыми, список туда не положить.