Перейти к содержанию

412 уроков, 25 библиотек и челлендж «Что выведет код?» — бесплатно, код прямо в браузере

Начать обучение
Урок 10 из 20 Начальный 15 мин 100 XP

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 нет вовсе.

Изменить нельзя

попытка add
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.

словарь тарифов с frozenset-ключами
fare = {
    frozenset({'Москва', 'Казань'}): 2400,
    frozenset({'Москва', 'Сочи'}): 3200,
}

print(fare[frozenset({'Сочи', 'Москва'})])
print(len(fare))
print(frozenset({'Казань', 'Сочи'}) in fare)
Вывод
3200
2
False

Ключ нашли, перечислив города в обратном порядке: {Сочи, Москва} и {Москва, Сочи} — одно множество, один ключ, один тариф. Так маршруты A-B и B-A превращаются в одну запись — удобно для цен, расстояний и рейсов. А третья проверка отвечает False: пары Казань и Сочи в словаре нет — тариф на этот маршрут ещё не задан.

попытка ключа-set
fare = {{'Москва', 'Казань'}: 2400}
Вывод
Traceback (most recent call last):
  File "app.py", line 1, in <module>
    fare = {{'Москва', 'Казань'}: 2400}
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: unhashable type: 'set'
Словарь даже не создаётся: ключ-множество не хешируется. Замените внутренние фигурные скобки на frozenset({...}) — и ключом станет постоянное множество.

Множество множеств

Вторая запретная операция для 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) копируют состав на момент вызова.

Возможностьsetfrozenset
add, remove, updateестьнет — AttributeError
in, len, sortedдада
|, &, -, ^дада — тип результата как у левого операнда
Ключ словарянет: unhashable typeда
Элемент другого множестванетда

Понадобилась изменяемая версия — разморозка тоже проста: set(fs) даёт обычный set с тем же составом:

обратно в 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))
Проверь себя
0 / 7

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?

Карточки терминов
Запомнено: 0 / 8
Практика

Составьте словарь distances с двумя frozenset-ключами: пары Питер и Тверь — 480, Тверь и Казань — 790. Выведите расстояние для пары, перечисленной в обратном порядке (Тверь, Питер), число записей и флаг: есть ли в словаре пара Питер и Казань.

practice.py
Вопросы и ответы по уроку

Что такое 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-канал или свой блог — так о проекте узнают новые читатели.

TelegramVK

Что дальше

Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.

Похожие уроки по темам

Подобраны автоматически по пересечению тем и ключевых слов.