Множества в Python: как задать set, операции и метод isdisjoint

Множества в Python: как задать set, операции и метод isdisjoint Полезное

Множество (set) в Python — это изменяемая коллекция уникальных хешируемых элементов без индексов и без гарантированного порядка. Задать множество можно фигурными скобками {1, 2, 3} или функцией set(); пустое множество — только set(), потому что {} создает словарь.

Ниже — как создавать множества, добавлять и удалять элементы, выполнять объединение, пересечение и разность, что именно проверяют isdisjoint(), issubset() и issuperset(), чем отличается frozenset и на чем чаще всего ошибаются. Все примеры и тексты ошибок получены на Python 3.14.7 (проверено 30.09.2026).

Минимальный пример: множество за десять строк

Задача: есть посетители двух дней, нужно узнать, сколько уникальных было в понедельник, кто приходил оба дня и кто только в понедельник.

mon = {"anna", "boris", "vera", "boris"}   # дубль "boris" пропадет
tue = set(["vera", "gleb"])                # множество из списка

print(len(mon))             # 3
print("anna" in mon)        # True
print(sorted(mon | tue))    # ['anna', 'boris', 'gleb', 'vera']
print(sorted(mon & tue))    # ['vera']
print(sorted(mon - tue))    # ['anna', 'boris']
print(mon.isdisjoint(tue))  # False

В консоли появятся шесть строк, указанных в комментариях. Дубль исчез при создании, проверка in ответила без перебора всего набора, а isdisjoint вернул False, потому что у двух дней есть общий элемент "vera".

sorted() здесь не украшение. Порядок обхода множества не определен, а для строк он меняется от запуска к запуску, поэтому для воспроизводимого вывода результат сортируют.

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

Способов три: литерал в фигурных скобках, функция set() от любого итерируемого объекта и генератор множества.

a = {1, 2, 3}                        # литерал
b = set([3, 3, 4])                   # из списка: {3, 4}
c = set("мама")                      # из строки: множество букв
d = set()                            # пустое множество
e = {x * x for x in range(-2, 3)}    # генератор множества

print(a, b, sorted(c), d, e)
# {1, 2, 3} {3, 4} ['а', 'м'] set() {0, 1, 4}
print(type({}), type(set()))
# <class 'dict'> <class 'set'>
print({"hello"}, set("hello") == {"h", "e", "l", "o"})
# {'hello'} True

Две детали, на которых спотыкаются. Первая: {} — пустой словарь, пустое множество записывается только как set(). Вторая: {"hello"} — множество из одной строки, а set("hello") — множество ее букв, потому что set() перебирает переданный объект по элементам.

Частая практическая задача — убрать дубликаты из списка: set(emails). Порядок при этом теряется. Если порядок первого появления важен, берут list(dict.fromkeys(emails)): словарь сохраняет порядок вставки.

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

s = {1, 2}
s.add(3)
s.add(3)                 # повторное добавление ничего не меняет
print(s)                 # {1, 2, 3}

s.update([4, 5], (6,))   # добавить сразу несколько, из любых итерируемых
print(s)                 # {1, 2, 3, 4, 5, 6}

s.discard(100)           # элемента нет - молча ничего не делает
s.remove(6)              # элемент есть - удаляет
print(s)                 # {1, 2, 3, 4, 5}

x = s.pop()              # достает и возвращает какой-то один элемент
print(len(s))            # 4
s.clear()
print(s)                 # set()
Метод Что делает Если элемента нет
add(x) добавляет один элемент —
update(it, ...) добавляет все элементы из итерируемых —
discard(x) удаляет x ничего не происходит
remove(x) удаляет x KeyError
pop() удаляет и возвращает произвольный элемент KeyError на пустом множестве
clear() удаляет все —

pop() часто называют «случайным», но это неточно: элемент произвольный, какой именно — зависит от внутреннего устройства таблицы, и рассчитывать на случайность нельзя. Для случайного выбора берут random.choice(list(s)).

Типичная ошибка с remove:

s = {1, 2}
s.remove(100)
KeyError: 100

Если отсутствие элемента — нормальная ситуация, используют s.discard(100). Если это признак ошибки в данных, оставляют remove и обрабатывают KeyError.

Операции: объединение, пересечение, разность

У каждой операции есть оператор, метод с новым результатом и метод, меняющий исходное множество.

Операция Оператор Метод На месте Результат
Объединение a | b a.union(b) a.update(b) все элементы из обоих
Пересечение a & b a.intersection(b) a.intersection_update(b) только общие
Разность a - b a.difference(b) a.difference_update(b) есть в a, нет в b
Симметрическая разность a ^ b a.symmetric_difference(b) a.symmetric_difference_update(b) есть ровно в одном из двух
a = {1, 2, 3, 4}
b = {3, 4, 5}

print(a | b)   # {1, 2, 3, 4, 5}
print(a & b)   # {3, 4}
print(a - b)   # {1, 2}
print(b - a)   # {5}
print(a ^ b)   # {1, 2, 5}

print(a.union([5, 6]))                # {1, 2, 3, 4, 5, 6}
print(a.intersection(range(3, 10)))   # {3, 4}

Разность несимметрична: a - b и b - a дают разные множества. Целые числа в выводе идут по возрастанию, но это особенность реализации для небольших чисел, а не обещание языка.

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

print({1, 2} | [3])
TypeError: unsupported operand type(s) for |: 'set' and 'list'

Исправление — {1, 2}.union([3]) либо {1, 2} | set([3]). Сложения через + у множеств нет, объединяют через |.

isdisjoint, issubset, issuperset: проверки отношений

Эти три метода ничего не меняют и возвращают True или False.

  • a.isdisjoint(b) — True, если у a и b нет ни одного общего элемента.
  • a.issubset(b) — True, если каждый элемент a есть в b (a — подмножество).
  • a.issuperset(b) — True, если каждый элемент b есть в a (a — надмножество).
need = {"python", "sql"}
have = {"python", "sql", "git"}
banned = {"php", "perl"}

print(have.isdisjoint(banned))           # True: общих нет
print(have.isdisjoint(["git", "go"]))    # False: общий "git"

print(need.issubset(have), need <= have, need < have)     # True True True
print(have.issuperset(need), have >= need, have > need)   # True True True
print(have <= have, have < have)                          # True False

print(set().isdisjoint(have), set().issubset(have))       # True True
print({1, 2} < {2, 3}, {1, 2} > {2, 3})                   # False False

if (common := have & need):
    print(sorted(common))                # ['python', 'sql']

Что здесь важно.

У isdisjoint нет оператора. Его аналог — not (a & b), но пересечение строит новое множество целиком, а isdisjoint только отвечает на вопрос. В CPython он прекращает перебор на первом общем элементе.

<= и < — разные проверки. a <= b совпадает с issubset и допускает равенство. a < b — строгое подмножество: a входит в b и не равно ему. Поэтому have <= have — True, а have < have — False. То же для >= и >.

Сравнение множеств — не сортировка. Для {1, 2} и {2, 3} ложны и <, и >, и ==: ни одно не входит в другое. Сортировать список множеств через sorted() без ключа бессмысленно.

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

Запись := в последнем примере — оператор присваивания внутри выражения (появился в Python 3.8): пересечение сохраняется в common и сразу проверяется на пустоту.

Как и у операций, методы принимают любой итерируемый объект, а операторы сравнения — только множества: {1} <= [1, 2] завершится TypeError.

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

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

f = frozenset([1, 2, 2, 3])
print(f, 2 in f)                     # frozenset({1, 2, 3}) True
print(f | {9})                       # frozenset({1, 2, 3, 9})

pairs = {frozenset({"a", "b"}), frozenset({"b", "a"})}
print(len(pairs))                    # 1: пара без учета порядка

cache = {frozenset({"x", "y"}): 10}
print(cache[frozenset({"y", "x"})])  # 10
print(frozenset({1, 2}) == {1, 2})   # True

Операции и проверки работают так же, как у set; тип результата смешанной операции берется от левого операнда. Попытка изменить завершится ошибкой: frozenset([1]).add(2) дает AttributeError: 'frozenset' object has no attribute 'add'.

set frozenset
Можно менять (add, remove, update) да нет
Хешируемо (ключ словаря, элемент множества) нет да
Литерал {1, 2} нет, только frozenset(...)

Типичные ошибки

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

s = {[1, 2], [3]}
TypeError: cannot use 'list' as a set element (unhashable type: 'list')

Исправление — кортежи: {(1, 2), (3,)}. Кортеж со списком внутри, например (1, [2]), тоже не пройдет. Текст сообщения приведен для Python 3.14; в Python 3.13 и более ранних он короче: TypeError: unhashable type: 'list'.

Обращение по индексу. s[0] дает TypeError: 'set' object is not subscriptable. Если нужен порядок или позиция, множество превращают в список: sorted(s).

Изменение во время обхода.

s = {1, 2, 3}
for x in s:
    if x % 2:
        s.remove(x)
RuntimeError: Set changed size during iteration

Исправление — строить новое множество: s = {x for x in s if x % 2 == 0}, результат {2}. Либо обходить копию: for x in list(s).

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

Копия по ссылке. u = t не создает новое множество: u.add(3) изменит и t. Независимая копия — t.copy().

Выводы

  • Множество задают литералом {1, 2}, функцией set(iterable) или генератором; пустое — только set(), {} — это словарь.
  • Элементы уникальны и хешируемы, порядка и индексов нет; для стабильного вывода используют sorted().
  • discard молча пропускает отсутствующий элемент, remove для отсутствующего элемента и pop на пустом множестве дают KeyError.
  • Операторы |, &, -, ^ работают только между множествами, методы union, intersection, isdisjoint, issubset принимают любой итерируемый объект.
  • isdisjoint проверяет отсутствие общих элементов, <= — подмножество, < — строгое подмножество; frozenset нужен там, где множество должно быть ключом или элементом.

Где применяется / связь с практикой

Множества в реальном коде решают три задачи: убрать дубликаты, быстро проверить принадлежность (в среднем за постоянное время вместо перебора списка) и сравнить два набора — права пользователя с требуемыми, теги, идентификаторы из двух выгрузок. Проверка «нет ли запрещенных значений» — это как раз isdisjoint, «хватает ли прав» — issubset.

Освойте тему на практике

Коллекции Python, включая множества и словари, с практикой на задачах разбирают на курсе Python-разработчик. Базовый уровень. Познакомиться с форматом занятий можно на открытых уроках Otus.

Смежные темы: множества как математическое понятие и их применение в программировании.

FAQ

Можно ли отсортировать множество?
Само множество — нет, у него нет порядка. sorted(s) вернет новый отсортированный список; если элементы разных типов, например числа и строки, сортировка завершится TypeError.

Что быстрее для проверки in: список или множество?
Множество: поиск идет по хешу и в среднем не зависит от размера, а список перебирается по элементам. На десятке элементов разницы не видно, на тысячах она существенна; точные цифры зависят от данных, их измеряют модулем timeit.

Почему порядок элементов при печати меняется между запусками?
Для строк хеш по умолчанию рандомизируется при каждом запуске интерпретатора, а порядок обхода множества зависит от хешей. Опираться на этот порядок нельзя ни для строк, ни для чисел.

Сохраняет ли множество порядок добавления, как словарь?
Нет. Словарь гарантирует порядок вставки с Python 3.7, множество такой гарантии не дает.

OTUS Журнал
Скидка 5% 12-19 октября на курсы (popup)
Бесплатные открытые уроки (поп-ап)