Справочник по Python · модуль 4 · словари, множества и кортежи
Глава 4.9
frozenset
в Python
Неизменяемый вариант множества. Отношение frozenset к set ровно такое же, как кортежа к списку: те же возможности чтения, никаких изменений — и, как следствие, хешируемость. Только благодаря ей набор может стать ключом словаря или элементом другого множества.
- Категория
- Структуры данных
- Уровень
- Средний
- Связанные темы
- Множества, хеширование, кортежи
- Зачем это знать
- Использовать набор как ключ и защищать состав от изменений
§1Зачем нужен ещё один тип
Глава 4.5 закончилась ограничением: множество нельзя положить внутрь другого множества и нельзя сделать ключом словаря. Причина известна из главы 4.1 — set изменяем, а значит не хешируем.
groups = {{1, 2}}
TypeError: unhashable type: 'set'
frozenset закрывает именно этот пробел. Он делает для множества то же, что кортеж делает для списка: убирает изменяемость и тем самым возвращает хешируемость.
list → tuple порядок сохраняется, менять нельзя set → frozenset уникальность сохраняется, менять нельзя
§2set и frozenset
| Свойство | set | frozenset |
|---|---|---|
| уникальные элементы | да | да |
| позиционный порядок | нет | нет |
перебор, len(), in | да | да |
операции |, &, -, ^ | да | да |
| изменение после создания | да | нет |
| хешируемость | нет | да |
| ключ словаря, элемент множества | нет | да |
| литерал | {1, 2} | только frozenset(...) |
§3Создание
Своего литерала у типа нет — только функция-конструктор, принимающая любую итерируемую коллекцию.
permissions = frozenset(["read", "write", "read"]) letters = frozenset("hello") empty = frozenset() print(len(permissions)) print(len(letters)) print(empty)
2 4 frozenset()
Повторы отсеиваются так же, как у set: из двух "read" остался один, а из пяти букв слова hello — четыре.
§4Чтение работает так же
roles = frozenset({"reader", "editor"}) print("reader" in roles) print(len(roles)) print(sorted(roles))
True 2 ['editor', 'reader']
Индексов по-прежнему нет: roles[0] даёт TypeError: 'frozenset' object is not subscriptable. Порядок обхода так же не определён, поэтому для воспроизводимого вывода нужен sorted().
§5Методов изменения нет
roles = frozenset({"reader"}) roles.add("admin")
AttributeError: 'frozenset' object has no attribute 'add'
Здесь AttributeError, а не TypeError: метод не запрещён — его просто не существует. Так же отсутствуют remove(), discard(), pop(), clear() и все методы с суффиксом _update из главы 4.8.
§6Операции создают новый набор
Операции из главы 4.7 доступны полностью — они и раньше не меняли исходные наборы.
base = frozenset({"read", "write"}) extended = base | {"delete"} print(sorted(extended)) print(sorted(base))
['delete', 'read', 'write'] ['read', 'write']
Недоступны только изменяющие версии: base |= {"delete"} вызовет ошибку, потому что это сокращение от base.update(...).
§7Тип результата
set и frozenset свободно смешиваются в операциях. Тип результата определяет левый операнд.
print(type(frozenset({1}) | {2})) print(type({1} | frozenset({2})))
<class 'frozenset'> <class 'set'>
Деталь незаметная, но важная: если результат должен остаться неизменяемым, слева должен стоять frozenset. Иначе получится обычный set, и его снова нельзя будет использовать как ключ.
§8Отношения множеств
required = frozenset({"read", "write"}) granted = frozenset({"read", "write", "delete"}) print(required.issubset(granted)) print(granted.issuperset(required)) print(required.isdisjoint({"ban"}))
True True True
Это делает frozenset удобным для наборов прав и ролей: состав зафиксирован типом, а все проверки достаточности остаются доступными.
§9Сравнение с set
Равенство определяется составом, а не типом.
mutable = {1, 2}
fixed = frozenset({2, 1})
print(mutable == fixed)
True
Порядок при создании тоже роли не играет: {1, 2} и {2, 1} — один и тот же набор. Это отличает frozenset от кортежа, где порядок значим, — сравнение будет в §12.
§10Ключ словаря
Главное применение типа. Ключом становится сам набор целиком, и порядок перечисления его не меняет.
prices = {
frozenset({"read"}): 100,
frozenset({"read", "write"}): 250,
}
print(prices[frozenset({"write", "read"})])
250
Обратите внимание: искали {"write", "read"}, а в словаре лежит {"read", "write"}. Для frozenset это одно и то же значение с одним и тем же хешем.
§11Элемент множества
Второе применение — множество множеств, невозможное с обычным set.
groups = {
frozenset({1, 2}),
frozenset({2, 1}),
frozenset({3}),
}
print(len(groups))
2
Уникальность работает и здесь: два первых набора равны по составу, поэтому в множестве остался один. Так удобно собирать, например, уникальные комбинации тегов или сочетания участников.
§12frozenset или tuple
Оба типа неизменяемы и хешируемы, поэтому оба годятся в ключи. Выбор определяется тем, важен ли порядок и допустимы ли повторы.
print(frozenset({1, 2}) == frozenset({2, 1})) print((1, 2) == (2, 1))
True False
| Нужно | Тип |
|---|---|
| порядок значим (координаты, дата) | tuple |
| повторы значимы | tuple |
| набор без порядка (права, теги) | frozenset |
| операции пересечения и разности | frozenset |
Практическое правило: если ключ — это запись из полей, берите кортеж; если набор из элементов, берите frozenset.
§13Практика: кеш по набору
Задача, где frozenset незаменим: запомнить результат вычисления для набора параметров, не зависящего от порядка.
cache = {}
key = frozenset({"b", "a"})
cache[key] = "результат"
print(cache[frozenset({"a", "b"})])
результат
С кортежем пришлось бы заранее сортировать элементы, чтобы ("a", "b") и ("b", "a") совпали. frozenset решает это сам.
§14Когда он действительно нужен
В повседневном коде frozenset встречается нечасто. Ситуаций, где он оправдан, всего несколько.
| Ситуация | Почему frozenset |
|---|---|
| набор как ключ словаря | set не хешируется |
| множество множеств | то же ограничение |
| константа с набором значений | тип запрещает случайную правку |
| набор передаётся в функцию | гарантия, что функция его не изменит |
Во всех остальных случаях подходит обычный set. Выбирать frozenset «на всякий случай» не стоит — он лишает возможности дополнять набор.
§15Частые ошибки
| Что написано | Что произойдёт | Как правильно |
|---|---|---|
roles.add(x) для frozenset | AttributeError: метода нет | roles = roles | {x} |
roles |= {x} | ошибка: это изменение на месте | roles = roles | {x} |
{{1, 2}} | TypeError: unhashable type: 'set' | {frozenset({1, 2})} |
{1} | frozenset({2}) для ключа | получится set — левый операнд решает | frozenset({2}) | {1} |
frozenset({1, [2]}) | TypeError: элемент не хешируется | все элементы должны быть неизменяемыми |
roles[0] | TypeError: индексов нет | перебрать или взять sorted() |
ждать литерал вида f{1, 2} | такого синтаксиса не существует | только frozenset({1, 2}) |
брать frozenset там, где набор пополняется | каждое изменение создаёт новый объект | обычный set |
§16Проверьте себя
Зачем нужен frozenset, если есть set?
Обычное множество изменяемо и потому не хешируется: его нельзя сделать ключом словаря или элементом другого множества. frozenset снимает это ограничение.
Почему roles.add(x) даёт AttributeError, а не TypeError?
Метод не запрещён — его просто не существует у типа, как append() у кортежа.
Какого типа будет результат {1} | frozenset({2})?
set: тип наследуется от левого операнда. Чтобы получить frozenset, он должен стоять слева.
Равны ли {1, 2} и frozenset({2, 1})?
Да. Равенство определяется составом, а не типом и не порядком перечисления.
Когда лучше кортеж, а когда frozenset?
Кортеж — если порядок или повторы значимы, то есть ключ представляет собой запись из полей. frozenset — если ключ это набор, где порядок не важен.
Можно ли создать frozenset литералом?
Нет, своего литерала у типа нет — только конструктор frozenset(...).
§17Шпаргалка
frozenset({1, 2})frozenset(items)frozenset()fs = fs | {x}prices[frozenset(perms)]{frozenset(a), frozenset(b)}fs.issubset(other)Итог главы
frozenset относится к set так же, как кортеж к списку: те же чтение, перебор и операции, но без единого метода изменения — и потому хешируемый. Это открывает два применения, недоступные обычному множеству: набор как ключ словаря и множество множеств. При смешивании типов результат наследует тип левого операнда, а равенство определяется составом, а не типом и не порядком. Выбирая между frozenset и кортежем, ориентируйтесь на смысл: набор без порядка или запись из полей.