Справочник по Python · модуль 4 · словари, множества и кортежи
Глава 4.8
Изменяющие операции
множеств
У каждой операции из главы 4.7 есть версия, которая меняет множество на месте вместо создания нового. Разница между a = a | b и a |= b кажется косметической — до тех пор, пока у множества не появится второе имя.
- Категория
- Операции set
- Уровень
- Средний
- Связанные темы
- Операции множеств, изменяемость, ссылки
- Зачем это знать
- Обновлять набор на месте и не терять исходные данные
§1Новый набор или изменение
Каждая операция главы 4.7 существует в двух видах. Один строит новый набор, другой меняет левый операнд.
| Операция | Новый результат | Изменение слева |
|---|---|---|
| объединение | a.union(b), a | b | a.update(b), a |= b |
| разность | a.difference(b), a - b | a.difference_update(b), a -= b |
| пересечение | a.intersection(b), a & b | a.intersection_update(b), a &= b |
| симметрическая разность | a.symmetric_difference(b), a ^ b | a.symmetric_difference_update(b), a ^= b |
Различить их легко по названию: суффикс _update и знак = у оператора означают «изменить на месте». Это та же пара, что sort() и sorted() в главе 3.11.
§2update(): добавить всё
Метод добавляет в множество все элементы аргумента. Повторы отсеиваются сами.
roles = {"reader"}
roles.update(["editor", "admin", "reader"])
print(sorted(roles))
['admin', 'editor', 'reader']
Метод уже встречался в главе 4.6 — он относится к add() так же, как extend() к append(). Здесь важно другое: update() и есть изменяющая версия объединения.
§3Оператор |=
Короткая запись того же действия.
roles = {"reader"}
roles |= {"editor"}
print(sorted(roles))
['editor', 'reader']
Запись a |= b читается как a += b у чисел: «взять текущее значение, применить операцию, положить обратно».
§4difference_update(): убрать
Метод удаляет из множества всё, что встретилось в аргументе.
permissions = {"read", "write", "delete"}
permissions.difference_update(["write", "unknown"])
print(sorted(permissions))
['delete', 'read']
Отсутствующие элементы ошибки не вызывают — метод ведёт себя как discard() из главы 4.6, только сразу для группы. Это удобно: не нужно проверять, что там есть на самом деле.
§5intersection_update(): оставить общее
Обратная задача: оставить только то, что есть и в аргументе.
requested = {"reports", "exports", "analytics"}
available = {"reports", "analytics"}
requested.intersection_update(available)
print(sorted(requested))
['analytics', 'reports']
Типичное применение — фильтрация: «оставить из запрошенного только разрешённое». Оператор &= делает то же самое короче.
§6symmetric_difference_update()
Метод оставляет то, что было ровно в одном из наборов, — получается переключение принадлежности.
selected = {"python", "sql"}
selected.symmetric_difference_update({"sql", "docker"})
print(sorted(selected))
['docker', 'python']
Общий элемент sql удалился, а docker добавился. Именно так работают переключатели в интерфейсах: повторный клик по выбранному тегу его снимает.
§7Сводная таблица
| Запись | Что остаётся в a |
|---|---|
a |= b | всё из обоих наборов |
a -= b | только то, чего нет в b |
a &= b | только общее с b |
a ^= b | то, что было ровно в одном |
Во всех четырёх случаях b не меняется — операция односторонняя.
§8Методы принимают списки
Различие между методом и оператором здесь то же, что в главе 4.7.
values = {1}
values |= [2]
TypeError: unsupported operand type(s) for |=: 'set' and 'list'
А values.update([2]) отработает без нареканий. Отсюда практический вывод: с данными из внешнего источника, которые обычно приходят списком, удобнее методы.
§9Несколько аргументов
Три метода из четырёх принимают сразу несколько коллекций.
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
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.
a = {1, 2}
b = a
a |= {3}
print(sorted(b))
[1, 2, 3]
a = {1, 2}
b = a
a = a | {3}
print(sorted(b))
print(sorted(a))
[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Практика: фильтрация набора
Типичный сценарий: собрать запрошенное, убрать запрещённое, оставить доступное.
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) | в переменной окажется None | values.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 |= ba.difference_update(items)a -= ba.intersection_update(items)a &= ba ^= bc = a | ba.update(items)result.update(new)Итог главы
У каждой операции множеств есть версия с суффиксом _update и оператор со знаком = — они меняют левый набор на месте и возвращают None. Правый набор при этом не меняется никогда. Выбор между a |= b и a = a | b становится существенным, когда у множества несколько имён: первое изменение увидят все, второе — только переменная слева. Методы принимают любые коллекции, операторы — только множества.