frozenset: неизменяемое множество в Python
Множество, которое нельзя менять: frozenset создаётся один раз, поддерживает все проверки и операции чтения и годится туда, куда set не пускают — ключом в словарь и элементом другого множества.
Редакция Питоники
До сих пор множества были изменяемыми: add и update из урока 3, remove и discard из урока 4. Иногда это не достоинство, а помеха: состав должен застыть — дни выходных, пары городов в маршруте, группы прав доступа. Для таких данных у Python есть frozenset — замороженное множество: состав фиксируется при создании и больше не меняется.
Создание: frozenset от коллекции
Создаётся так же, как set через set() в уроке 2, только функцией frozenset:
routes = frozenset(['Москва', 'Казань', 'Сочи'])
print(sorted(routes))
print(len(routes))
print(type(routes))
['Казань', 'Москва', 'Сочи'] 3 <class 'frozenset'>
Внутри — то же множество: уникальные элементы, мгновенный in, печать через sorted. Отличие в типе и в правах: методов изменения у frozenset нет вовсе.
Изменить нельзя
weekend = frozenset(['сб', 'вс'])
try:
weekend.add('пн')
except AttributeError as e:
print('AttributeError:', e)
print(len(weekend))
AttributeError: 'frozenset' object has no attribute 'add' 2
У frozenset нет ни add, ни remove, ни discard, ни update — попытка вызвать любой из них упадёт с AttributeError. Это не недоработка, а гарантия: состав нельзя случайно испортить в другом месте программы.
Читать и считать можно всё
weekend = frozenset(['сб', 'вс'])
workdays = frozenset(['пн', 'вт', 'ср', 'чт', 'пт'])
print('сб' in weekend)
print(len(weekend | workdays))
print(sorted(weekend & workdays))
print(type(weekend | workdays))
True 7 [] <class 'frozenset'>
Проверки и операции работают как у set: in отвечает мгновенно, объединение и пересечение считаются, разность и симметричная разность тоже. Результат наследует тип левого операнда: frozenset | frozenset — снова frozenset, а если слева обычный set, получится set.
Ключ словаря: то, чего set не умеет
Словарь вычисляет хеш ключа один раз и дальше полагается на его постоянство. Обычный set меняется — хеш разъехался бы с содержимым; Python запрещает такие ключи вовсе. frozenset постоянен, поэтому годится: парой городов удобно описывать маршрут — направление не важно, едет ли кто-то из Казани в Москву или обратно, тариф тот же. Подробно о хешах и о том, что у множеств и словарей общего, — в уроке 17.
fare = {
frozenset({'Москва', 'Казань'}): 2400,
frozenset({'Москва', 'Сочи'}): 3200,
}
print(fare[frozenset({'Сочи', 'Москва'})])
print(len(fare))
print(frozenset({'Казань', 'Сочи'}) in fare)
3200 2 False
Ключ нашли, перечислив города в обратном порядке: {Сочи, Москва} и {Москва, Сочи} — одно множество, один ключ, один тариф. Так маршруты A-B и B-A превращаются в одну запись — удобно для цен, расстояний и рейсов. А третья проверка отвечает False: пары Казань и Сочи в словаре нет — тариф на этот маршрут ещё не задан.
fare = {{'Москва', 'Казань'}: 2400}
Traceback (most recent call last):
File "app.py", line 1, in <module>
fare = {{'Москва', 'Казань'}: 2400}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: unhashable type: 'set'Множество множеств
Вторая запретная операция для set — вкладывание: множество не может быть элементом другого множества, по той же причине нехешируемости. frozenset вкладывается без проблем — так собирают группы:
groups = {
frozenset(['чай', 'кофе']),
frozenset(['хлеб', 'булка']),
frozenset(['чай', 'кофе']),
}
print(len(groups))
print(sum(len(g) for g in groups))
print(frozenset(['кофе', 'чай']) in groups)
2 4 True
Первая и третья группы совпали по составу и схлопнулись в одну — уникальность работает и здесь. Проверка in нашла группу, перечислив её элементы в другом порядке: равные множества — это один элемент.
Здесь прячется частое недопонимание. frozenset не замораживает существующее множество, а строит новое: последующие изменения исходника в него не попадут.
base = {'а', 'б'}
snapshot = frozenset(base)
base.add('в')
print(sorted(snapshot))
print(sorted(base))
['а', 'б'] ['а', 'б', 'в']
Снимок остался без в — он сделан до добавления. Так устроены все конструкторы коллекций: list(x), set(x), tuple(x) копируют состав на момент вызова.
| Возможность | set | frozenset |
|---|---|---|
| add, remove, update | есть | нет — AttributeError |
| in, len, sorted | да | да |
| |, &, -, ^ | да | да — тип результата как у левого операнда |
| Ключ словаря | нет: unhashable type | да |
| Элемент другого множества | нет | да |
Понадобилась изменяемая версия — разморозка тоже проста: set(fs) даёт обычный set с тем же составом:
fs = frozenset(['а', 'б'])
mutable = set(fs)
mutable.add('в')
print(sorted(mutable))
print(sorted(fs))
['а', 'б', 'в'] ['а', 'б']
Исходный frozenset не тронут: set(fs) собрал независимую копию. Заморозка и разморозка — два конструктора, каждый делает новый объект.
Что дальше
frozenset — первая неизменяемая структура курса: все права чтения, ноль прав записи. Дальше её собрат по неизменности, но с порядком: в уроке 11 — кортеж tuple, фиксированный набор значений с доступом по индексу и без права на переписывание.
Сначала предскажи ответ в голове — это главный навык программиста.
fs = frozenset(['a', 'b', 'c'])
print(len(fs))
print('b' in fs)
print(sorted(fs - {'a'}))
cost = {frozenset({'x', 'y'}): 10, frozenset({'y', 'z'}): 20}
print(cost[frozenset({'y', 'x'})])
print(len(cost))
s = {'a', 'b'}
snapshot = frozenset(s)
s.add('c')
print(len(snapshot))
print(sorted(s) == sorted(snapshot))
1. Чем frozenset отличается от set?
2. Что произойдёт при вызове fs.add('x'), если fs — frozenset?
3. Что вернёт сравнение {'а'} == frozenset({'а'})?
4. Почему set нельзя ключом словаря?
5. В словаре есть ключ frozenset({'b', 'a'}). Что вернёт d[frozenset({'a', 'b'})]?
6. Что вернёт len({frozenset({1, 2}), frozenset({2, 1})})?
7. Как получить изменяемую копию frozenset?
Составьте словарь distances с двумя frozenset-ключами: пары Питер и Тверь — 480, Тверь и Казань — 790. Выведите расстояние для пары, перечисленной в обратном порядке (Тверь, Питер), число записей и флаг: есть ли в словаре пара Питер и Казань.
Что такое frozenset простыми словами?
Множество-константа: frozenset создаётся из коллекции один раз и больше не меняется — add, remove и update у него отсутствуют. При этом все проверки in, len и операции объединения, пересечения и разности работают как у обычного set.
Урок 2: создание множествКогда использовать frozenset вместо set?
Когда состав должен застыть: фиксированные группы, маршруты, права доступа. Плюс два уникальных права: frozenset можно положить ключом в словарь и элементом в другое множество — обычный set туда не пускают.
Почему set нельзя ключом словаря, а frozenset можно?
Словарь хеширует ключ при добавлении и полагается на его постоянство. set изменяем — после add хеш разъехался бы с содержимым, поэтому Python запрещает его вовсе (TypeError: unhashable type: 'set'). frozenset не меняется, его хеш постоянен — он годится.
Урок 17: множества и словариКак frozenset сделать изменяемым?
set(fs) — новый обычный set с тем же составом. Исходный frozenset при этом не трогается: конструктор всегда строит независимый объект.
Замораживает ли frozenset(x) само множество x?
Нет: это снимок состава на момент вызова. Дальнейшие изменения x в замороженный снимок не попадут. Зафиксировать состав на будущее — значит работать с результатом frozenset(x), а не с исходником.
Понравился урок? Сошлитесь на него
«frozenset не замораживает существующее множество, а строит новое: последующие изменения исходника в него не попадут.»
Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.
Что дальше
Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.
Что читать дальше
Множества · Урок 2
Создание множеств: {} и set() — литерал, список, строка
Два способа создать множество: литерал в фигурных скобках и set() от списка или строки. Пустые скобки — это словарь, поэтому пустое множество пишут set().
Множества · Урок 11
Tuple в Python: неизменяемый кортеж и доступ по индексу
Кортеж хранит фиксированный набор значений и защищает его от изменений: создали — и состав застыл. Создание, доступ по индексу, IndexError и то, почему запятая главнее скобок.
Множества · Урок 17
Множества и словари Python: hash и общие механизмы
Множество и словарь построены на одном механизме: hash назначает каждому элементу полку, и проверка in становится мгновенной. Ключи словаря — готовое множество, над которым работают & и |.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
Словари · Урок 18
Ключи: что можно и что нельзя
Ключом бывают строки, числа и кортежи, но не список: TypeError: unhashable type объясняется устройством словаря, а frozenset спасает, когда нужен набор.
ключи словаря pythonunhashable type list
ООП · Урок 12
Сравнение объектов: __eq__ и __hash__
По умолчанию == для своих классов работает как is: два одинаковых счёта оказываются «не равны». Определяем __eq__ по полям, добавляем __hash__ — и объекты работают в словарях и множествах.
объект как ключ словаря pythonunhashable type python
Множества · Урок 12
Кортеж против списка в Python: что и когда использовать
У обоих есть индексы, len и in, но список можно менять, а кортеж — нет. Четыре отличия: изменяемость, ключ словаря, память и правило выбора за три секунды.
кортеж ключ словаря pythonразница кортеж список