Множество (set) в Python — это изменяемая коллекция уникальных хешируемых элементов без индексов и без гарантированного порядка. Задать множество можно фигурными скобками {1, 2, 3} или функцией set(); пустое множество — только set(), потому что {} создает словарь.
Содержание
- Минимальный пример: множество за десять строк
- Как задать множество в Python
- Добавление и удаление элементов
- Операции: объединение, пересечение, разность
- isdisjoint, issubset, issuperset: проверки отношений
- frozenset: неизменяемое множество
- Типичные ошибки
- Выводы
- Где применяется / связь с практикой
- FAQ
Ниже — как создавать множества, добавлять и удалять элементы, выполнять объединение, пересечение и разность, что именно проверяют 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, множество такой гарантии не дает.



