PythonJuniorTechnical

Чем 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 упорядоченным: итерация идёт в хэш-порядке, не в порядке вставки.
  • Класть в frozenset mutable-элементы (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.
  • Знает операции множеств.
  • Понимает отсутствие сортировки.

Sources

Related topics