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

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

Глава 4.6

4.6

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

Методы для работы с отдельными элементами: add() добавляет, remove() и discard() удаляют по-разному, pop() снимает произвольный элемент. Выбор между строгим и мягким удалением — главное решение этой главы.

Категория
Методы set
Уровень
Базовый
Связанные темы
Множества, исключения, копирование
Зачем это знать
Изменять состав множества и не ловить лишних исключений

§1Знакомые операции

Часть работы с множеством выглядит так же, как с любой коллекцией.

ЗаписьРезультат
len(values)число элементов
value in valuesпроверка наличия
for value in valuesперебор без гарантии порядка
values.clear()очистка исходного множества
values.copy()новое поверхностное множество

Новое здесь — методы изменения состава. Их немного, но у двух из них одинаковая работа и разное поведение при отсутствии элемента.

§2add(): один элемент

main.pyновая роль
roles = {"reader", "editor"}
roles.add("admin")

print(sorted(roles))
print("admin" in roles)
Выводэлемент появился
['admin', 'editor', 'reader']
True

Название неслучайно: у множества нет позиции «в конце», поэтому здесь add(), а не списочный append(). Попытка вызвать append() даёт AttributeError.

§3Повторное добавление

Если элемент уже есть, метод молча ничего не делает.

main.py102 уже в множестве
ids = {101, 102}
ids.add(102)

print(len(ids))
Выводэлементов по-прежнему два
2

Метод не сообщает, был ли элемент новым: он всегда возвращает None. Если это нужно знать, проверяют заранее через in — как в примере с обработанными задачами из главы 4.5.

§4update(): сразу несколько

Чтобы добавить группу элементов, есть update() — он относится к add() так же, как extend() к append() у списков в главе 3.10.

main.pyдобавление из списка
values = {1, 2}
values.update([3, 4])

print(sorted(values))
Выводдобавлены оба элемента
[1, 2, 3, 4]

Аргументом может быть любая итерируемая коллекция — список, кортеж, строка, другое множество. Повторы при этом отсеются сами.

§5remove(): строгое удаление

main.pyудаляем то, чего нет
roles = {"reader", "editor"}
roles.remove("guest")
Ошибкаэлемента в множестве не было
KeyError: 'guest'

Тип исключения тот же, что у словаря, — и это логично: множество и есть словарь без значений. Строгость полезна, когда отсутствие элемента означает нарушение ожидаемого состояния программы.

§6discard(): мягкое удаление

Тот же результат, но без исключения. Повторный вызов безопасен.

main.pyудаляем дважды и удаляем несуществующее
roles = {"reader", "editor"}

roles.discard("editor")
roles.discard("editor")
roles.discard("guest")

print(roles)
Выводни одной ошибки
{'reader'}

Такое поведение называют идемпотентным: сколько раз ни вызови — результат один. Для «убедиться, что элемента нет» это ровно то, что нужно.

§7remove() или discard()

МетодЭлемент естьЭлемента нетКогда выбирать
remove(x)удалитKeyErrorотсутствие — ошибка данных
discard(x)удалитничего не сделаетнужно гарантировать отсутствие

Это то же решение, что между data[key] и data.get(key) в главе 4.2, и между index() и find() в главе 5.5. Вопрос всегда один: считать ли отсутствие ошибкой.

Практическое правило

Если сразу после удаления идёт if x in values — вы уже выбрали discard(), просто написали это в две строки. Проверка перед remove() почти всегда означает, что нужен был discard().

§8pop(): произвольный элемент

У множества нет порядка, поэтому pop() — не «последний», как у списка, а какой придётся.

main.pyкакой элемент снимется — неизвестно
queue = {"task-1", "task-2", "task-3"}
task = queue.pop()

print(len(queue))
Выводэлементов стало на один меньше
2

Метод возвращает удалённый элемент, а на пустом множестве вызывает KeyError: 'pop from an empty set'. Имя переменной queue в примере намеренно неудачное: для очереди множество не годится — порядок обработки предсказать нельзя.

§9clear() и присваивание

main.pyочистка на месте
active_ids = {101, 102, 103}
active_ids.clear()

print(active_ids)
Выводпустое множество печатается так
set()

Пустое множество выводится как set(), а не как {} — по той же причине, что и в главе 4.5: фигурные скобки заняты словарём.

Разница между values.clear() и values = set() точно такая же, как у списков в главе 3.11: первый опустошает сам объект, и это видят все его имена, второй создаёт новое пустое множество только для одной переменной.

§10copy(): отдельное множество

main.pyизменение копии не трогает оригинал
base = {"read", "write"}
custom = base.copy()
custom.add("delete")

print(sorted(base))
print(sorted(custom))
Выводдва независимых множества
['read', 'write']
['delete', 'read', 'write']

Присваивание custom = base копией не является — это второе имя того же объекта, как разбиралось в главе 3.10. Про «поверхностность» копии здесь можно не беспокоиться: элементы множества неизменяемы по определению, поэтому вложенных уровней, которые остались бы общими, просто не бывает.

§11Что возвращают методы

МетодМеняет множествоВозвращает
add()даNone
update()даNone
remove()даNone
discard()даNone
clear()даNone
pop()даудалённый элемент
copy()нетновое множество

Правило то же, что у списков: метод, меняющий коллекцию, возвращает None. Поэтому values = values.add(x) уничтожит множество, оставив в переменной None. Исключения — pop() и copy(), они отдают результат.

§12Изменение во время перебора

Множество ведёт себя как словарь из главы 4.2: менять его размер во время цикла нельзя.

main.pyдобавление внутри перебора
values = {1, 2, 3}

for value in values:
    values.add(99)
Ошибкаперебор прерван
RuntimeError: Set changed size during iteration

Здесь Python строже, чем со списком: там аналогичная ошибка из главы 3.11 приводила к тихо неверному результату, а тут программа останавливается сразу. Если менять состав всё-таки нужно, перебирают копию: for value in values.copy().

§13Практика: подписки

main.pyподписка, отписка и отписка несуществующего
subscribers = {101, 205}

subscribers.add(310)
subscribers.discard(205)
subscribers.discard(999)

print(310 in subscribers)
print(len(subscribers))
Выводосталось два подписчика
True
2

Здесь discard() выбран сознательно: пользователь может нажать «отписаться» дважды или отписаться от того, на что не был подписан, и падать программе из-за этого не следует. А вот remove() уместен там, где отсутствие означает рассогласование данных — например, при удалении задачи из списка активных, которая туда точно попадала.

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

Что написаноЧто произойдётКак правильно
values.append(x)AttributeError: у множества нет такого методаvalues.add(x)
values.add([1, 2])TypeError: список не хешируетсяvalues.add((1, 2))
values.add(items) для группыдобавится один объект или будет ошибкаvalues.update(items)
values.remove(x), когда x может не бытьKeyErrorvalues.discard(x)
first = values.pop() с ожиданием первогоснимется произвольный элементвзять список или кортеж
values = values.add(x)в переменной окажется Nonevalues.add(x) отдельной строкой
copy = originalоба имени обозначат одно множествоcopy = original.copy()
менять состав внутри forRuntimeErrorперебирать values.copy()

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

Почему у множества add(), а не append()?

У множества нет порядка и позиции «в конце», поэтому «добавить в конец» не имеет смысла.

Когда remove() лучше discard()?

Когда отсутствие элемента означает ошибку состояния, которую нельзя молча пропускать.

Что вернёт values.add(x), если элемент уже был?

None — как и всегда. Метод не сообщает, был ли элемент новым; для этого нужна проверка in заранее.

Какой элемент снимет pop()?

Произвольный: порядка у множества нет. На пустом множестве метод вызывает KeyError.

Почему values = values.add(x) — ошибка?

add() меняет множество на месте и возвращает None, поэтому переменная потеряет коллекцию.

Чем добавление группы отличается от добавления одного элемента?

add() кладёт объект целиком, update() перебирает аргумент и добавляет каждый его элемент — как append() и extend() у списков.

§16Шпаргалка

Добавитьvalues.add(item)
Добавить группуvalues.update(items)
Удалить строгоvalues.remove(item)
Удалить безопасноvalues.discard(item)
Снять любойitem = values.pop()
Очиститьvalues.clear()
Копияother = values.copy()
Менять в циклеfor v in values.copy():

Итог главы

add() добавляет один элемент и молчит, если он уже есть; update() добавляет сразу группу. Главное решение главы — между remove() и discard(): первый считает отсутствие ошибкой, второй нет, и выбор диктуется требованиями программы, а не удобством. pop() снимает произвольный элемент, потому что порядка у множества нет. Все методы изменения возвращают None, а менять состав множества во время перебора нельзя — Python прервёт цикл ошибкой.

Глава 4.6 · Методы множеств · Справочник по Python Автор: Макаров Максим Николаевич