Чем set отличается от frozenset? Почему set нельзя положить внутрь другого set?
set изменяемый и не hashable, frozenset неизменяемый и hashable. Положить set в set нельзя: hash-таблица требует стабильный hash, а у изменяемого set его быть не может.
Различия set и frozenset
set — изменяемое множество уникальных hashable-элементов. Поддерживает add(), remove(), discard(), update(), pop(), операции |, &, -, ^ с присваиванием (|= и т.п.). Сам set не hashable: вызов hash(s) приведёт к TypeError: unhashable type: 'set'.
frozenset — неизменяемая версия. Те же операции чтения и неизменяемые версии бинарных операций (union, intersection, difference, symmetric_difference), но без add/remove. Объект имеет стабильный __hash__, поэтому может быть ключом dict или элементом другого set/frozenset.
Почему set нельзя вложить в set
Внутри CPython множество — это open-addressing hash-таблица (см. Objects/setobject.c). При вставке элемента вызывается PyObject_Hash. Если бы элементом был обычный set, его hash зависел бы от содержимого, а содержимое можно менять через add. Тогда после первой вставки последующий in-поиск мог бы попасть в другой бакет и не найти существующий элемент. Поэтому язык запрещает hashing для mutable-контейнеров, а frozenset разрешает.
Пример
s = {1, 2, 3}
s.add(4)
fs = frozenset({1, 2, 3})
# fs.add(4) # AttributeError: 'frozenset' object has no attribute 'add'
groups = {frozenset({"py", "sql"}), frozenset({"go"})}
print(frozenset({"sql", "py"}) in groups) # True
index = {frozenset({1, 2}): "pair"}
print(index[frozenset({2, 1})]) # 'pair'
try:
{{1, 2}}
except TypeError as exc:
print(exc) # unhashable type: 'set'
Когда что выбирать
set— накопитель уникальных значений, дедупликация в пайплайне, кэш посещённых id.frozenset— ключ словаря, элемент другого множества, защищённый snapshot прав/ролей, ключ вfunctools.lru_cache.
Подводные камни
- Считать
frozensetупорядоченным: итерация идёт в хэш-порядке, не в порядке вставки. - Класть в
frozensetmutable-элементы (list,dict):TypeErrorещё на этапе создания. - Сериализация: при JSON-дампе и
set, иfrozensetтребуют преобразования вlist. - Сравнение по identity (
is) вместо равенства (==): дваfrozensetс одинаковым содержимым обычно разные объекты. - Полагаться на dict в качестве элемента:
frozenset({{"a": 1}})упадёт — словарь не hashable. - Изменять элементы
frozensetчерез ссылки на сами объекты (если положилиtupleс mutable-полем — сноваTypeErrorили нестабильный hash).
Common mistakes
- Говорить, что frozenset нужен только для оптимизации.
- Не связывать set с hashable-элементами.
- Пытаться положить set внутрь set.
What the interviewer is testing
- Объясняет hashability set и frozenset.
- Знает операции множеств.
- Понимает отсутствие сортировки.