Модуль 4 · Словари, множества и кортежи

Справочник по Python · модуль 4 · словари, множества и кортежи

Глава 4.8

4.8

Изменяющие операции
множеств

У каждой операции из главы 4.7 есть версия, которая меняет множество на месте вместо создания нового. Разница между a = a | b и a |= b кажется косметической — до тех пор, пока у множества не появится второе имя.

Категория
Операции set
Уровень
Средний
Связанные темы
Операции множеств, изменяемость, ссылки
Зачем это знать
Обновлять набор на месте и не терять исходные данные

§1Новый набор или изменение

Каждая операция главы 4.7 существует в двух видах. Один строит новый набор, другой меняет левый операнд.

ОперацияНовый результатИзменение слева
объединениеa.union(b), a | ba.update(b), a |= b
разностьa.difference(b), a - ba.difference_update(b), a -= b
пересечениеa.intersection(b), a & ba.intersection_update(b), a &= b
симметрическая разностьa.symmetric_difference(b), a ^ ba.symmetric_difference_update(b), a ^= b

Различить их легко по названию: суффикс _update и знак = у оператора означают «изменить на месте». Это та же пара, что sort() и sorted() в главе 3.11.

§2update(): добавить всё

Метод добавляет в множество все элементы аргумента. Повторы отсеиваются сами.

main.pyreader уже был в наборе
roles = {"reader"}
roles.update(["editor", "admin", "reader"])

print(sorted(roles))
Выводтри роли, без дублей
['admin', 'editor', 'reader']

Метод уже встречался в главе 4.6 — он относится к add() так же, как extend() к append(). Здесь важно другое: update() и есть изменяющая версия объединения.

§3Оператор |=

Короткая запись того же действия.

main.pyсправа обязательно множество
roles = {"reader"}
roles |= {"editor"}

print(sorted(roles))
Выводроль добавлена
['editor', 'reader']

Запись a |= b читается как a += b у чисел: «взять текущее значение, применить операцию, положить обратно».

§4difference_update(): убрать

Метод удаляет из множества всё, что встретилось в аргументе.

main.pyотзыв разрешений
permissions = {"read", "write", "delete"}
permissions.difference_update(["write", "unknown"])

print(sorted(permissions))
Выводwrite убран, unknown проигнорирован
['delete', 'read']

Отсутствующие элементы ошибки не вызывают — метод ведёт себя как discard() из главы 4.6, только сразу для группы. Это удобно: не нужно проверять, что там есть на самом деле.

§5intersection_update(): оставить общее

Обратная задача: оставить только то, что есть и в аргументе.

main.pyобрезаем запрос по доступному тарифу
requested = {"reports", "exports", "analytics"}
available = {"reports", "analytics"}

requested.intersection_update(available)

print(sorted(requested))
Выводнедоступное отброшено
['analytics', 'reports']

Типичное применение — фильтрация: «оставить из запрошенного только разрешённое». Оператор &= делает то же самое короче.

§6symmetric_difference_update()

Метод оставляет то, что было ровно в одном из наборов, — получается переключение принадлежности.

main.pyпереключение выбранных тегов
selected = {"python", "sql"}
selected.symmetric_difference_update({"sql", "docker"})

print(sorted(selected))
Выводsql выключен, docker включён
['docker', 'python']

Общий элемент sql удалился, а docker добавился. Именно так работают переключатели в интерфейсах: повторный клик по выбранному тегу его снимает.

§7Сводная таблица

ЗаписьЧто остаётся в a
a |= bвсё из обоих наборов
a -= bтолько то, чего нет в b
a &= bтолько общее с b
a ^= bто, что было ровно в одном

Во всех четырёх случаях b не меняется — операция односторонняя.

§8Методы принимают списки

Различие между методом и оператором здесь то же, что в главе 4.7.

main.pyсправа от оператора список
values = {1}
values |= [2]
Ошибкаоператор требует множество
TypeError: unsupported operand type(s) for |=: 'set' and 'list'

А values.update([2]) отработает без нареканий. Отсюда практический вывод: с данными из внешнего источника, которые обычно приходят списком, удобнее методы.

§9Несколько аргументов

Три метода из четырёх принимают сразу несколько коллекций.

main.pyпересечение трёх условий
ids = {1, 2, 3, 4}
ids.intersection_update([2, 3, 4], (3, 4, 5))

print(sorted(ids))
Выводобщее для всех трёх наборов
[3, 4]
МетодНесколько аргументов
update()да
difference_update()да
intersection_update()да
symmetric_difference_update()нет, ровно один

Исключение объяснимо: симметрическая разность для трёх наборов сразу не имеет однозначного смысла. Попытка передать два аргумента даёт TypeError: takes exactly one argument.

§10Все возвращают None

main.pyрезультат вызова и состояние множества
values = {1, 2}
result = values.update([3])

print(result)
print(sorted(values))
Выводработа сделана, но не возвращена
None
[1, 2, 3]

Правило из главы 3.10 действует и здесь: метод, меняющий коллекцию, возвращает None. Запись values = values.update([3]) уничтожила бы множество.

§11Почему это важно: два имени

Пока у множества одно имя, выбор между a = a | b и a |= b ни на что не влияет. Разница проявляется, когда имён два, — как в главе 3.10.

main.pyизменение на месте
a = {1, 2}
b = a

a |= {3}

print(sorted(b))
Выводb видит изменение
[1, 2, 3]
main.pyсоздание нового множества
a = {1, 2}
b = a

a = a | {3}

print(sorted(b))
print(sorted(a))
Выводb остался со старым набором
[1, 2]
[1, 2, 3]
Две почти одинаковые строки

a |= b меняет сам объект — изменение видят все имена. a = a | b создаёт новое множество и связывает с ним только a; прежний объект остаётся у остальных имён. Если множество передавали в функцию или сохраняли под другим именем, разница становится решающей.

§12Как выбрать

СитуацияЧто использовать
старое состояние больше не нужноa |= b или a.update(b)
исходный набор ещё понадобитсяc = a | b
множество известно под несколькими именамирешить осознанно: изменение увидят все
справа список или кортежметод, а не оператор
нужно накапливать в циклеresult.update(...)

Последняя строка — про эффективность: result = result | new в цикле создаёт новое множество на каждом шаге, как склейка строк в главе 5.9.

§13Практика: фильтрация набора

Типичный сценарий: собрать запрошенное, убрать запрещённое, оставить доступное.

main.pyтри изменения одного набора
requested = {"reports", "exports"}
extra = ["analytics", "admin"]
banned = {"admin"}
available = {"reports", "analytics", "exports"}

requested.update(extra)
requested.difference_update(banned)
requested.intersection_update(available)

print(sorted(requested))
Выводитоговый набор разрешённого
['analytics', 'exports', 'reports']

Каждый шаг меняет один и тот же набор, промежуточные копии не создаются. Если бы понадобилось сохранить исходный запрос, первую строку заменили бы на result = requested | set(extra).

§14Частые ошибки

Что написаноЧто произойдётКак правильно
values = values.update(items)в переменной окажется Nonevalues.update(items) отдельной строкой
values |= [2]TypeError: оператору нужно множествоvalues.update([2])
a |= b, когда a ещё нужен прежнимисходный набор потерянc = a | b
a = a | b с ожиданием, что увидят все именадругие имена остались со старым множествомa |= b
symmetric_difference_update(x, y)TypeError: только один аргументвызвать метод дважды
a.difference_update(b) с ожиданием изменения bменяется только левый набороперация односторонняя
result = result | new в цикленовое множество на каждом шагеresult.update(new)

§15Проверьте себя

Чем a |= b отличается от a = a | b?

Первое меняет сам объект, и изменение видят все его имена. Второе создаёт новое множество и связывает с ним только a.

Что возвращают методы с суффиксом _update?

None. Они меняют множество на месте, поэтому присваивать их результат нельзя.

Почему values |= [2] не работает?

Оператор требует множество с обеих сторон. Метод update() принимает любую итерируемую коллекцию.

Меняется ли правый набор при a -= b?

Нет. Все изменяющие операции односторонние: меняется только левый набор.

Какой метод не принимает нескольких аргументов?

symmetric_difference_update() — для трёх наборов сразу эта операция не имеет однозначного смысла.

Что удобнее для накопления результата в цикле?

result.update(...): операция result = result | new создавала бы новое множество на каждом шаге.

§16Шпаргалка

Добавить всёa.update(items)a |= b
Убратьa.difference_update(items)a -= b
Оставить общееa.intersection_update(items)a &= b
Переключитьa ^= b
Сохранить исходноеc = a | b
Со спискомa.update(items)
Накопление в циклеresult.update(new)

Итог главы

У каждой операции множеств есть версия с суффиксом _update и оператор со знаком = — они меняют левый набор на месте и возвращают None. Правый набор при этом не меняется никогда. Выбор между a |= b и a = a | b становится существенным, когда у множества несколько имён: первое изменение увидят все, второе — только переменная слева. Методы принимают любые коллекции, операторы — только множества.

Глава 4.8 · Изменяющие операции множеств · Справочник по Python Автор: Макаров Максим Николаевич