Справочник по Python · модуль 4 · словари, множества и кортежи
Глава 4.6
Методы
множеств
Методы для работы с отдельными элементами: add() добавляет, remove() и discard() удаляют по-разному, pop() снимает произвольный элемент. Выбор между строгим и мягким удалением — главное решение этой главы.
- Категория
- Методы set
- Уровень
- Базовый
- Связанные темы
- Множества, исключения, копирование
- Зачем это знать
- Изменять состав множества и не ловить лишних исключений
§1Знакомые операции
Часть работы с множеством выглядит так же, как с любой коллекцией.
| Запись | Результат |
|---|---|
len(values) | число элементов |
value in values | проверка наличия |
for value in values | перебор без гарантии порядка |
values.clear() | очистка исходного множества |
values.copy() | новое поверхностное множество |
Новое здесь — методы изменения состава. Их немного, но у двух из них одинаковая работа и разное поведение при отсутствии элемента.
§2add(): один элемент
roles = {"reader", "editor"}
roles.add("admin")
print(sorted(roles))
print("admin" in roles)
['admin', 'editor', 'reader'] True
Название неслучайно: у множества нет позиции «в конце», поэтому здесь add(), а не списочный append(). Попытка вызвать append() даёт AttributeError.
§3Повторное добавление
Если элемент уже есть, метод молча ничего не делает.
ids = {101, 102}
ids.add(102)
print(len(ids))
2
Метод не сообщает, был ли элемент новым: он всегда возвращает None. Если это нужно знать, проверяют заранее через in — как в примере с обработанными задачами из главы 4.5.
§4update(): сразу несколько
Чтобы добавить группу элементов, есть update() — он относится к add() так же, как extend() к append() у списков в главе 3.10.
values = {1, 2}
values.update([3, 4])
print(sorted(values))
[1, 2, 3, 4]
Аргументом может быть любая итерируемая коллекция — список, кортеж, строка, другое множество. Повторы при этом отсеются сами.
§5remove(): строгое удаление
roles = {"reader", "editor"}
roles.remove("guest")
KeyError: 'guest'
Тип исключения тот же, что у словаря, — и это логично: множество и есть словарь без значений. Строгость полезна, когда отсутствие элемента означает нарушение ожидаемого состояния программы.
§6discard(): мягкое удаление
Тот же результат, но без исключения. Повторный вызов безопасен.
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() — не «последний», как у списка, а какой придётся.
queue = {"task-1", "task-2", "task-3"}
task = queue.pop()
print(len(queue))
2
Метод возвращает удалённый элемент, а на пустом множестве вызывает KeyError: 'pop from an empty set'. Имя переменной queue в примере намеренно неудачное: для очереди множество не годится — порядок обработки предсказать нельзя.
§9clear() и присваивание
active_ids = {101, 102, 103}
active_ids.clear()
print(active_ids)
set()
Пустое множество выводится как set(), а не как {} — по той же причине, что и в главе 4.5: фигурные скобки заняты словарём.
Разница между values.clear() и values = set() точно такая же, как у списков в главе 3.11: первый опустошает сам объект, и это видят все его имена, второй создаёт новое пустое множество только для одной переменной.
§10copy(): отдельное множество
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: менять его размер во время цикла нельзя.
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Практика: подписки
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 может не быть | KeyError | values.discard(x) |
first = values.pop() с ожиданием первого | снимется произвольный элемент | взять список или кортеж |
values = values.add(x) | в переменной окажется None | values.add(x) отдельной строкой |
copy = original | оба имени обозначат одно множество | copy = original.copy() |
менять состав внутри for | RuntimeError | перебирать 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 прервёт цикл ошибкой.