Какие элементы могут быть в множестве в Python
Что проверяют
Знаете ли вы, что ограничение у множества не по типу, а по свойству объекта: элемент должен быть хэшируемым. Следом почти всегда спрашивают, почему кортеж можно положить в set, а список нельзя, и что будет с экземпляром вашего класса.
Как отвечать
set устроен как хэш-таблица: при добавлении Python считает hash(x), находит ячейку и при коллизии сравнивает элементы через ==. Отсюда требование: у объекта есть __hash__, согласованный с __eq__, и хэш не меняется, пока объект лежит в множестве.
- можно:
int,float,complex,str,bytes,bool,None,frozenset, а также кортеж, если все его элементы хэшируемы; - нельзя:
list,dict,set,bytearray. Попытка даётTypeError: unhashable type; - кортеж с изменяемым внутри:
(1, [2])тоже падает сTypeError, хэшируемость кортежа определяется содержимым; - свои классы: по умолчанию хэшируются по идентичности, два разных экземпляра с одинаковыми полями останутся двумя элементами. Если вы определили
__eq__и не определили__hash__, Python выставит__hash__ = None, и класс станет нехэшируемым; - dataclass:
@dataclass(frozen=True)генерирует__hash__по полям, а обычный@dataclassсeq=Trueпо умолчанию делает экземпляры нехэшируемыми.
Множество множеств собирается через frozenset. Поиск и добавление в среднем O(1), поэтому x in s на больших коллекциях заметно быстрее, чем поиск по списку.
Где спотыкаются
Говорят «в множестве могут быть только неизменяемые типы». Критерий точнее: хэшируемость. Экземпляр обычного класса изменяем, но в set кладётся без проблем.
Не знают про равенство разных типов: {1, 1.0, True} превращается в {1}, потому что 1 == 1.0 == True и хэши у них совпадают.
Полагаются на порядок. Маленькие целые часто выводятся по возрастанию, и это похоже на сортировку, а хэш строк рандомизирован между запусками интерпретатора, так что порядок обхода меняется от запуска к запуску. Заодно путают литералы: {} это пустой словарь, пустое множество создаётся через set().