Множества set в Python: что это и как с ними работать
Коротко. Множество (set) — это изменяемая коллекция уникальных неупорядоченных элементов. Дубликаты в нее попасть не могут, обращения по индексу нет, зато проверка наличия элемента работает почти мгновенно независимо от размера коллекции. Создают множество фигурными скобками {1, 2, 3} или функцией set(), а хранить внутри можно только неизменяемые объекты — числа, строки, кортежи.
Зачем нужны множества
У новичка обычно есть списки, и кажется, что их хватает на все. Но три задачи списки решают плохо, а множества — отлично.
Убрать дубликаты. Спарсили тысячу телефонов, а среди них половина повторов. Одна строка — и повторов нет.
Быстро проверить наличие. Есть база из миллиона email, и нужно понять, встречается ли в ней конкретный адрес. В списке Python будет перебирать элементы по одному, во множестве — найдет ответ сразу.
Сравнить две коллекции. Кто из вчерашних подписчиков отписался сегодня, какие теги есть у обоих товаров, какие города встречаются в первом отчете и отсутствуют во втором. Все это решается одним оператором.
emails = ["a@mail.uz", "b@mail.uz", "a@mail.uz"]
unique = set(emails)
print(unique)
# {'a@mail.uz', 'b@mail.uz'}
Три свойства множества
Уникальность. Каждый элемент встречается ровно один раз. Попытка добавить дубликат ничего не изменит и ошибки не вызовет.
Неупорядоченность. У элементов нет позиций и порядкового номера. Обратиться к my_set[0] нельзя, срезы тоже не работают.
Изменяемость. Элементы можно добавлять и удалять — в отличие от кортежа.
Отдельно стоит понимать, откуда берется скорость. Множество хранит не сами значения, а их хеши — короткие числовые отпечатки. Чтобы проверить наличие элемента, Python считает его хеш и сразу заглядывает в нужное место, а не перебирает коллекцию. Именно поэтому во множество можно положить только неизменяемые объекты: у изменяемого хеш поменялся бы вместе с содержимым, и элемент «потерялся» бы внутри структуры.
Как создать множество
Фигурными скобками — самый короткий способ:
courses = {"Python", "QA", "Frontend"}
Функцией set() из любого итерируемого объекта:
print(set([1, 2, 2, 3])) # {1, 2, 3}
print(set((1, 2, 3))) # {1, 2, 3}
print(set("привет")) # {'п', 'р', 'и', 'в', 'е', 'т'}
print(set(range(1, 4))) # {1, 2, 3}
print(set({"a": 1, "b": 2})) # {'a', 'b'} — только ключи
Генератором множества — по аналогии с генератором списка, но в фигурных скобках:
squares = {x ** 2 for x in range(10) if x % 2 == 0}
print(squares) # {0, 4, 16, 36, 64}
Важная ловушка с пустым множеством. Пустые фигурные скобки создают не множество, а словарь:
empty = {}
print(type(empty)) # <class 'dict'>
empty = set()
print(type(empty)) # <class 'set'>
Пустое множество создается только через set(). Это одна из самых частых ошибок новичков.
Что можно положить во множество
Только неизменяемые объекты: числа, строки, кортежи, frozenset, None, логические значения.
valid = {1, 3.14, "текст", (1, 2), None, True}
Списки, словари и обычные множества внутрь не поместить:
{1, [2, 3]}
# TypeError: unhashable type: 'list'
Ошибка unhashable type означает ровно это: объект изменяемый, посчитать для него постоянный хеш нельзя.
Если вложенная коллекция все же нужна, преобразуйте ее в кортеж или frozenset:
pairs = {(1, 2), (3, 4)}
groups = {frozenset([1, 2]), frozenset([3, 4])}
Неочевидная деталь. Python считает равными 1, 1.0 и True — у них одинаковый хеш. Поэтому во множестве останется только первое добавленное:
print({1, True, 1.0}) # {1}
Это редко мешает, но иногда объясняет, почему из набора «пропал» элемент.
Добавление элементов
add() добавляет один элемент:
courses = {"Python", "QA"}
courses.add("Frontend")
courses.add("Python") # уже есть, ничего не изменится
print(len(courses)) # 3
update() добавляет сразу несколько из любого итерируемого объекта:
courses = {"Python"}
courses.update(["QA", "Frontend"])
courses.update({"Blender"})
print(courses)
# {'Python', 'QA', 'Frontend', 'Blender'}
Осторожно со строкой: как и extend у списков, update разберет ее на символы.
tags = set()
tags.update("smm")
print(tags) # {'s', 'm'} — не то, что ожидалось
Для добавления строки целиком нужен add().
Удаление элементов
Способов четыре, и они различаются поведением.
remove() удаляет элемент и падает с ошибкой KeyError, если его нет:
courses = {"Python", "QA"}
courses.remove("QA")
courses.remove("Blender") # KeyError: 'Blender'
discard() делает то же самое, но молчит, если элемента не было. Для данных из внешних источников он безопаснее.
pop() удаляет произвольный элемент и возвращает его. Какой именно — предсказать нельзя, потому что порядка у множества нет. На пустом множестве вызывает KeyError.
clear() полностью очищает множество.
Операции над множествами
Здесь начинается самое интересное — то, ради чего множества чаще всего и берут. У каждой операции есть два способа записи: оператор и метод.
Объединение — все элементы обоих множеств:
group_a = {"Азиз", "Дилноза", "Тимур"}
group_b = {"Тимур", "Малика"}
print(group_a | group_b)
print(group_a.union(group_b))
# {'Азиз', 'Дилноза', 'Тимур', 'Малика'}
Пересечение — только общие элементы:
print(group_a & group_b)
print(group_a.intersection(group_b))
# {'Тимур'}
Разность — элементы первого, которых нет во втором:
print(group_a - group_b)
print(group_a.difference(group_b))
# {'Азиз', 'Дилноза'}
Порядок здесь важен: group_b - group_a даст уже {'Малика'}.
Симметрическая разность — элементы, которые есть только в одном из множеств:
print(group_a ^ group_b)
print(group_a.symmetric_difference(group_b))
# {'Азиз', 'Дилноза', 'Малика'}
Разница между оператором и методом , о которой почти нигде не пишут: метод принимает любой итерируемый объект, а оператор — только множество.
group_a.union(["Малика", "Санжар"]) # работает
group_a | ["Малика"] # TypeError
Так что при работе со списками методы удобнее — не нужно предварительно все преобразовывать.
У каждой операции есть версия, которая меняет множество на месте: update, intersection_update, difference_update, symmetric_difference_update, а также операторы |=, &=, -=, ^=.
Проверка наличия и сравнение
Наличие элемента проверяется оператором in:
courses = {"Python", "QA"}
print("Python" in courses) # True
print("Blender" not in courses) # True
Множества можно сравнивать между собой:
- == — множества полностью совпадают по составу, порядок значения не имеет
- <= и issubset() — все элементы первого есть во втором
- < — то же самое, но множества не равны
- >= и issuperset() — первое содержит второе целиком
- isdisjoint() — у множеств нет ни одного общего элемента
juniors = {"Python", "SQL"}
required = {"Python", "SQL", "Git"}
print(juniors <= required) # True — подмножество
print(required.issuperset(juniors)) # True
print(juniors.isdisjoint({"Java"})) # True — пересечений нет
Обратите внимание: == проверяет полное равенство, а не наличие общих элементов. Чтобы узнать, пересекаются ли множества, используйте & или isdisjoint().
Насколько множества быстрее списков
Это главный практический аргумент в пользу set.
Чтобы проверить наличие элемента в списке, Python перебирает его с начала: в худшем случае придется просмотреть все элементы. Во множестве проверка сводится к вычислению хеша и одному обращению в память — время не зависит от размера коллекции.
На десяти элементах разницы не видно. На списке из миллиона значений проверка in может занимать миллисекунды, а на множестве той же длины — доли микросекунды, то есть в тысячи раз быстрее.
Отсюда рабочий прием: если внутри цикла есть проверка «а есть ли этот элемент в коллекции», и коллекция большая — превратите ее в множество заранее.
# медленно
blacklist = load_blacklist() # список из 100 000 адресов
for email in emails:
if email in blacklist:
...
# быстро
blacklist = set(load_blacklist())
for email in emails:
if email in blacklist:
...
Платой за скорость становится память: множество занимает заметно больше места, чем список с теми же данными. Для типовых задач это не проблема, но на очень больших объемах стоит держать в голове.
Удаление дубликатов и потеря порядка
Самый популярный способ применения множеств выглядит так:
names = ["Азиз", "Дилноза", "Азиз", "Тимур"]
unique = list(set(names))
Работает, но есть подвох: порядок при этом теряется. Множество не хранит последовательность, и на выходе элементы могут оказаться в любом порядке. Иногда результат случайно совпадает с исходным, и новичок делает неверный вывод, что порядок сохраняется. Полагаться на это нельзя.
Если порядок важен, используйте словарь — с версии Python 3.7 он гарантированно запоминает порядок добавления ключей:
names = ["Азиз", "Дилноза", "Азиз", "Тимур"]
unique = list(dict.fromkeys(names))
print(unique)
# ['Азиз', 'Дилноза', 'Тимур']
Этот прием стоит запомнить: он решает ту же задачу, но без потери последовательности.
frozenset: неизменяемое множество
frozenset — версия множества, которую нельзя менять после создания. Методов add, remove и update у него нет, зато доступны все операции сравнения и объединения.
tags = frozenset(["python", "backend"])
tags.add("sql") # AttributeError
print(tags | {"sql"}) # frozenset({'python', 'backend', 'sql'})
Зачем он нужен, если есть обычный set. Ответ в хешируемости: раз frozenset неизменяем, его можно использовать там, где обычное множество не подходит — как ключ словаря или как элемент другого множества.
permissions = {
frozenset(["read"]): "Читатель",
frozenset(["read", "write"]): "Редактор",
}
Второе применение — защита данных. Если набор не должен меняться (роли, допустимые значения, конфигурация), frozenset гарантирует, что никакая функция его случайно не испортит.
Практические примеры
Что изменилось между двумя выгрузками. Классическая задача аналитика: сравнить вчерашний и сегодняшний список клиентов.
yesterday = {"A101", "A102", "A103"}
today = {"A102", "A103", "A104"}
print(today - yesterday) # {'A104'} — новые
print(yesterday - today) # {'A101'} — ушедшие
print(today & yesterday) # общие
print(today ^ yesterday) # все изменения разом
Проверка обязательных навыков в вакансии.
required = {"Python", "SQL", "Git"}
candidate = {"Python", "SQL", "Excel"}
missing = required - candidate
if missing:
print("Не хватает навыков:", ", ".join(missing))
Фильтрация стоп-слов в тексте.
stop_words = {"и", "в", "на", "с", "по"}
text = "курсы по python в ташкенте"
words = [w for w in text.split() if w not in stop_words]
print(words) # ['курсы', 'python', 'ташкенте']
Стоп-слова здесь именно множество, а не список: проверка внутри цикла выполняется для каждого слова, и на большом тексте разница ощутима. Про разбор строки на слова подробнее есть в статье про метод split.
Поиск дубликатов, а не их удаление.
values = [1, 2, 3, 2, 4, 1]
seen = set()
duplicates = set()
for value in values:
if value in seen:
duplicates.add(value)
seen.add(value)
print(duplicates) # {1, 2}
Частые ошибки
Пустые фигурные скобки. {} создает словарь, а не множество. Нужен set().
Попытка обратиться по индексу. my_set[0] вызовет TypeError — у множества нет позиций. Чтобы перебрать элементы, используйте цикл for, а чтобы получить конкретный — преобразуйте в список.
Ожидание сохраненного порядка. Порядок вывода множества не определен и может меняться между запусками. Для упорядоченного результата используйте sorted().
Список внутри множества. Вызывает TypeError: unhashable type. Преобразуйте в кортеж.
update со строкой. Разбирает строку на символы. Для добавления строки целиком нужен add().
Оператор вместо метода со списком. my_set | [1, 2] не сработает, а my_set.union([1, 2]) — да.
remove вместо discard. Если элемента может не быть, remove уронит программу с KeyError.
Изменение множества во время перебора. Добавление или удаление элементов внутри цикла по этому же множеству вызывает RuntimeError. Собирайте изменения отдельно и применяйте после цикла.
Когда брать множество, а когда список
- Важен порядок элементов или нужны индексы — список
- Могут быть повторы и они значимы — список
- Нужна уникальность — множество
- Часто проверяется наличие элемента в большой коллекции — множество
- Нужно сравнить две коллекции, найти общее или различия — множество
- Данные не должны меняться и должны быть ключом словаря — frozenset
- Нужны пары «ключ — значение» — словарь
Коротко о главном
Множество в Python хранит уникальные элементы, не помнит их порядка и не дает обращаться по индексу. Взамен оно почти мгновенно отвечает на вопрос «есть ли здесь такой элемент» и умеет за один оператор находить объединение, пересечение и разность двух коллекций.
Три вещи, которые стоит запомнить сразу: пустое множество создается только через set(), внутрь можно класть лишь неизменяемые объекты, а при удалении дубликатов через set теряется исходный порядок — если он нужен, берите dict.fromkeys().
Понимание структур данных и того, какая из них подходит под задачу, отличает работающий код от быстрого. Освоить Python с нуля до уровня реальных проектов можно на курсе Python в PROWEB — 8 месяцев, с 14 лет, с практикой и поддержкой в трудоустройстве. Если вы еще выбираете направление в IT, пройдите бесплатный IT-тест — он подскажет, что вам ближе.
Часто задаваемые вопросы
Что такое множество в Python?
Это изменяемая коллекция уникальных неупорядоченных элементов. Дубликаты в нее не попадают, обращение по индексу не поддерживается, зато проверка наличия элемента работает очень быстро благодаря хешированию.
Чем множество отличается от списка?
Список упорядочен, допускает повторы и позволяет обращаться к элементам по индексу. Множество не хранит порядок, автоматически убирает дубликаты и не имеет индексов, но проверяет наличие элемента значительно быстрее.
Как создать пустое множество?
Только функцией set(). Пустые фигурные скобки {} создают словарь — это одна из самых распространенных ошибок новичков.
Почему нельзя добавить список во множество?
Потому что элементы хранятся по хешу, а у изменяемого объекта хеш непостоянен. Python выдаст ошибку TypeError: unhashable type: 'list'. Если нужна вложенная коллекция, преобразуйте список в кортеж или frozenset.
Как удалить дубликаты из списка и сохранить порядок?
Через list(dict.fromkeys(items)). Обычное преобразование в множество дубликаты уберет, но исходный порядок при этом потеряется.
Чем отличаются remove и discard?
Оба удаляют элемент, но remove вызывает ошибку KeyError, если элемента нет, а discard в этой ситуации ничего не делает. Для данных из внешних источников безопаснее discard.
Чем frozenset отличается от set?
frozenset неизменяем: добавлять и удалять элементы нельзя. Зато он хешируемый, поэтому может быть ключом словаря или элементом другого множества.
Как найти общие элементы двух списков?
Преобразовать оба в множества и взять пересечение: set(a) & set(b). Тот же результат даст метод intersection, который к тому же принимает список напрямую, без преобразования.
Сохраняется ли порядок элементов во множестве?
Нет. Порядок вывода не определен и может отличаться между запусками программы. Если нужен предсказуемый порядок, применяйте sorted() при выводе.
Насколько множества быстрее списков при поиске?
Поиск в списке требует перебора элементов, а во множестве сводится к вычислению хеша и одному обращению в память. На больших коллекциях разница достигает тысяч раз, на маленьких она незаметна.



