Django-Admin.ru

Множества в Python (set): методы и операции

Множество в Python (set): создание, add и remove, проверка in, объединение и пересечение, удаление дубликатов из списка, frozenset и частые ошибки.

Редакция Django-Admin.ru
· обновлено · 10 мин чтения

Содержание · 13 разделов
  1. Как создать множество
  2. Какие элементы можно хранить
  3. Добавление и удаление элементов
  4. Проверка вхождения: оператор in
  5. Операции над множествами
  6. Пример: что изменилось между двумя списками
  7. Сравнение множеств: подмножество и надмножество
  8. Методы множества Python: шпаргалка
  9. Как удалить дубликаты из списка
  10. Генератор множеств (set comprehension)
  11. Неизменяемое множество frozenset
  12. Обход множества циклом
  13. Частые ошибки
  14. Пустые скобки создают словарь
  15. Обращение по индексу
  16. Ожидание порядка элементов
  17. Изменение множества во время обхода
  18. update() со строкой
  19. 1 и True — один и тот же элемент
  20. Частые вопросы
  21. Как получить элемент множества, не удаляя его?
  22. Как превратить множество в список или строку?
  23. Как найти общие элементы нескольких списков?
  24. Когда выбирать множество вместо списка?
  25. Итог

Множество в Python (set) — неупорядоченная коллекция уникальных элементов. Короткий ответ: set([1, 2, 2, 3]) вернёт {1, 2, 3}, проверка x in s работает быстро даже на миллионе элементов, а операторы |, & и - дают объединение, пересечение и разность двух множеств.

Ниже — как создать множество, добавлять и удалять элементы, какие операции и методы есть у set, как убрать дубликаты из списка и чем set отличается от frozenset. Все примеры проверены на Python 3.12; вывод показан комментарием # →.

Как создать множество

Множество записывают в фигурных скобках или получают функцией set() из любой коллекции. Повторы отбрасываются автоматически:

numbers = {3, 1, 2, 3, 1}
print(numbers)        # → {1, 2, 3}
print(type(numbers))  # → <class 'set'>
print(len(numbers))   # → 3

from_list = set([5, 5, 6, 7, 7])
print(from_list)      # → {5, 6, 7}
empty = set()         # пустое множество
print(empty)          # → set()
print(type({}))       # → <class 'dict'>

Пустые фигурные скобки {} создают словарь, а не множество. Пустое множество — только set().

set() из строки разбивает её на отдельные символы:

letters = set("банан")
print(sorted(letters))  # → ['а', 'б', 'н']
print(len(letters))     # → 3

Здесь вывод обёрнут в sorted(): порядок строк внутри множества меняется от запуска к запуску, а отсортированный список всегда одинаков.

Какие элементы можно хранить

Элементом множества может быть только хешируемый объект: число, строка, кортеж из хешируемых значений, None. Список, словарь или другое множество положить в set нельзя:

ok = {1, "два", (3, 4), None}
print(len(ok))  # → 4
bad = {[1, 2], 3}  # → TypeError: unhashable type: 'list'

Это то же ограничение, что и у ключей словаря: множество устроено как хеш-таблица, только без значений.

Добавление и удаление элементов

s = {1, 2, 3}
s.add(4)              # добавить один элемент
s.add(2)              # уже есть — ничего не изменится
s.update([5, 6], {7}) # добавить элементы из одной или нескольких коллекций
print(s)              # → {1, 2, 3, 4, 5, 6, 7}

s.remove(7)           # удалить; KeyError, если элемента нет
s.discard(100)        # удалить, если есть; без ошибки
print(s)              # → {1, 2, 3, 4, 5, 6}
x = s.pop()           # удалить и вернуть произвольный элемент
print(len(s))         # → 5
s.clear()
print(s)              # → set()

Разница между remove() и discard() — только в реакции на отсутствующий элемент:

s = {1, 2}
s.remove(3)  # → KeyError: 3

Если элемента может не быть, берите discard(). Метод pop() возвращает произвольный элемент — не «последний» и не «первый»: порядка у множества нет.

Проверка вхождения: оператор in

Главное практическое преимущество множества — быстрая проверка «есть ли элемент». В списке in перебирает элементы по очереди, а в множестве находит ответ по хешу за постоянное в среднем время:

banned = {"spam", "scam", "fraud"}
print("spam" in banned)      # → True
print("hello" not in banned) # → True

Если в цикле вы много раз проверяете вхождение в большой список, один раз превратите его в множество: allowed = set(big_list).

Операции над множествами

Python поддерживает операции из математики множеств — как операторами, так и методами:

a = {1, 2, 3, 4}
b = {3, 4, 5, 6}

print(a | b)  # → {1, 2, 3, 4, 5, 6}   объединение
print(a & b)  # → {3, 4}               пересечение
print(a - b)  # → {1, 2}               разность: есть в a, нет в b
print(b - a)  # → {5, 6}
print(a ^ b)  # → {1, 2, 5, 6}         симметрическая разность

Методы делают то же самое, но принимают любой итерируемый объект, а операторы работают только с множествами:

a = {1, 2, 3, 4}
print(a.union([4, 5]))              # → {1, 2, 3, 4, 5}
print(a.intersection(range(3, 10))) # → {3, 4}
print(a.difference((1, 2), [3]))     # → {4}
print(a | [4, 5])  # → TypeError: unsupported operand type(s) for |: 'set' and 'list'

У каждой операции есть вариант «на месте», который меняет исходное множество:

tags = {1, 2, 3}
tags |= {4}                    # то же, что tags.update({4})
tags &= {2, 3, 4, 5}           # оставить только общие
tags -= {3}                    # убрать элементы
print(tags)                    # → {2, 4}

Пример: что изменилось между двумя списками

Разность и пересечение удобно применять для сравнения двух наборов данных — например, списка пользователей вчера и сегодня:

old = ["anna", "oleg", "ivan"]
new = ["oleg", "ivan", "petr"]
print(sorted(set(new) - set(old)))  # → ['petr']   добавились
print(sorted(set(old) - set(new)))  # → ['anna']   ушли
print(sorted(set(old) & set(new)))  # → ['ivan', 'oleg']  остались

Сравнение множеств: подмножество и надмножество

admins = {"anna", "oleg"}
staff = {"anna", "oleg", "ivan"}

print(admins <= staff)          # → True  подмножество
print(admins.issubset(staff))   # → True
print(staff >= admins)          # → True  надмножество
print(admins < staff)           # → True  строгое подмножество
print(staff < staff)            # → False
print(admins.isdisjoint({"petr"}))  # → True  нет общих элементов
print({1, 2} == {2, 1})          # → True  порядок не важен

Методы множества Python: шпаргалка

Метод / операторЧто делает
s.add(x)добавляет один элемент
s.update(*others), s |= tдобавляет элементы из других коллекций
s.remove(x)удаляет элемент, KeyError если его нет
s.discard(x)удаляет элемент, если он есть
s.pop()удаляет и возвращает произвольный элемент
s.clear()очищает множество
s.union(*others), s | tобъединение
s.intersection(*others), s & tпересечение
s.difference(*others), s - tразность
s.symmetric_difference(t), s ^ tэлементы, которые есть только в одном из множеств
s.issubset(t), s <= tвсе элементы s есть в t
s.issuperset(t), s >= tвсе элементы t есть в s
s.isdisjoint(t)True, если общих элементов нет
s.copy()поверхностная копия
len(s), x in sразмер, проверка вхождения

Для intersection, difference и symmetric_difference есть версии *_update, которые меняют множество на месте. Полный справочник — в документации по типам set и frozenset.

Как удалить дубликаты из списка

Самый частый повод взять set — убрать повторы:

items = [3, 1, 3, 2, 1, 5]
print(len(set(items)))                # → 4
print(list(dict.fromkeys(items)))     # → [3, 1, 2, 5]
print(sorted(set(items)))             # → [1, 2, 3, 5]
print(len(items) != len(set(items)))  # → True  есть дубликаты
  • list(set(items)) убирает дубликаты, но теряет порядок элементов.
  • list(dict.fromkeys(items)) убирает дубликаты и сохраняет порядок первого появления: ключи словаря уникальны и идут в порядке вставки.
  • sorted(set(items)) — уникальные значения по возрастанию.

Сравнение длин списка и множества — быстрый способ проверить, есть ли в списке повторы.

Генератор множеств (set comprehension)

Множество можно собрать одной строкой по шаблону {выражение for x in коллекция}:

words = ["Python", "python", "PYTHON", "Django"]
unique = {w.lower() for w in words}
print(sorted(unique))   # → ['django', 'python']
lengths = {len(w) for w in words}
print(lengths)          # → {6}

Синтаксис совпадает с генератором словаря, но без двоеточия ключ: значение.

Неизменяемое множество frozenset

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

fs = frozenset([1, 2, 3])
print(fs)              # → frozenset({1, 2, 3})
print(fs | {4})        # → frozenset({1, 2, 3, 4})
routes = {frozenset({"Москва", "Казань"}): 820}
print(routes[frozenset({"Казань", "Москва"})])  # → 820
fs.add(4)  # → AttributeError: 'frozenset' object has no attribute 'add'

Операции над множествами для frozenset работают и возвращают новый frozenset. Похожая пара «изменяемый — неизменяемый» есть у списка и кортежа.

Обход множества циклом

Множество перебирается обычным циклом for, но порядок обхода не гарантирован. Если он важен, обходите sorted(s):

for n in sorted({30, 10, 20}):
    print(n)
10
20
30

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

Пустые скобки создают словарь

x = {}
x.add(1)  # → AttributeError: 'dict' object has no attribute 'add'

Пустое множество — только set().

Обращение по индексу

s = {10, 20, 30}
s[0]  # → TypeError: 'set' object is not subscriptable

У множества нет индексов и срезов. Нужен порядок и доступ по номеру — превратите в список: sorted(s)[0] вернёт наименьший элемент.

Ожидание порядка элементов

Не полагайтесь на то, в каком порядке print() выводит множество. Небольшие целые числа часто выглядят «отсортированными», но это деталь реализации; для строк порядок меняется между запусками программы. Когда порядок важен, используйте sorted() или dict.fromkeys().

Изменение множества во время обхода

s = {1, 2, 3, 4}
for n in s:
    if n % 2 == 0:
        s.remove(n)  # → RuntimeError: Set changed size during iteration

Соберите новое множество генератором:

s = {1, 2, 3, 4}
s = {n for n in s if n % 2}
print(s)  # → {1, 3}

update() со строкой

update() перебирает аргумент, поэтому строка добавится по символам. Чтобы добавить строку целиком, используйте add():

s = set()
s.update("abc")
print(sorted(s))   # → ['a', 'b', 'c']
s2 = set()
s2.add("abc")
print(s2)          # → {'abc'}

1 и True — один и тот же элемент

1 == True и их хеши совпадают, поэтому {1, True, 1.0} — множество из одного элемента {1}. Учитывайте это, если храните в одном множестве числа и логические значения.

Частые вопросы

Как получить элемент множества, не удаляя его?

Индексов нет, поэтому элемент берут из итератора: next(iter(s)). Какой элемент вернётся, заранее неизвестно, — для множества из одного элемента это удобный способ достать значение.

s = {42}
print(next(iter(s)))  # → 42
print(len(s))         # → 1

Как превратить множество в список или строку?

list(s) даёт список в произвольном порядке, sorted(s) — отсортированный список. Строку из строковых элементов собирают через join, предварительно отсортировав, чтобы результат не менялся между запусками.

tags = {"django", "api", "python"}
print(sorted(tags))             # → ['api', 'django', 'python']
print(", ".join(sorted(tags)))  # → api, django, python

Как найти общие элементы нескольких списков?

Превратите первый список в множество и передайте остальные в intersection(): метод принимает сразу несколько коллекций, и превращать каждую в set не нужно. Так же работает union() для объединения.

a, b, c = [1, 2, 3, 4], [2, 3, 4, 5], [3, 4, 6]
print(set(a).intersection(b, c))  # → {3, 4}

Когда выбирать множество вместо списка?

Когда важны уникальность и частые проверки x in ..., а порядок и повторы не нужны. Список сохраняет порядок, допускает дубликаты и доступ по индексу. Если нужны и уникальность, и порядок, используйте ключи словаря: dict.fromkeys(items).

Итог

Множество нужно в трёх случаях: убрать дубликаты, быстро проверять вхождение через in и сравнивать наборы операциями |, &, -, ^. Помните, что у set нет порядка и индексов, а пустое множество создаётся только через set(). Остальные встроенные типы и конструкции собраны в шпаргалке по синтаксису Python; соседние коллекции — списки, словари и кортежи.

  • #Python
  • #set
  • #Множества
  • #Синтаксис
  • #Коллекции
↑ ↓ выборEnter открытьEsc закрыть