Множества в Python: что должен знать новичок с первого дня
Множества в Python убирают дубликаты одной строкой и проверяют вхождение элемента в тысячи раз быстрее списка. Разбираем, где они выручают и где подводят.

Множество (set) в Python решает две задачи, которые в списке пишутся коряво и работают медленно: убрать дубликаты и проверить, есть ли элемент в коллекции. Через 15 минут вы будете понимать, когда брать set вместо list, почему из множества нельзя достать «третий элемент» и на каких данных оно ломается. Примеры проверены на Python 3.12, но работают начиная с 3.6.
Что такое множество за одну минуту
Множество — это неупорядоченная коллекция уникальных элементов. Две ключевые особенности вытекают прямо из определения:
- Дубликатов не бывает. Добавили один и тот же элемент дважды — он останется в единственном экземпляре.
- Порядка нет. Нельзя обратиться к элементу по индексу:
my_set[0]вызовет ошибку.
Создаётся множество фигурными скобками или функцией set():
numbers = {3, 1, 2, 2, 3}
print(numbers) # {1, 2, 3} — дубликаты ушли, порядок вывода не гарантирован
empty = set() # пустое множество
letters = set("hello") # {'h', 'e', 'l', 'o'}
Первая ловушка: пустые фигурные скобки — это не множество
Запись {} создаёт пустой словарь, а не множество. Это классическая ошибка первого дня.
x = {}
print(type(x)) # <class 'dict'>
y = set()
print(type(y)) # <class 'set'>
Запомните правило: пустое множество создаётся только через set(). Фигурные скобки без содержимого всегда дают словарь.
Зачем это нужно: убрать дубликаты
Самая частая причина, по которой новичок впервые тянется к множеству — очистить список от повторов. Одна строка вместо цикла с проверкой:
emails = ["a@x.ru", "b@x.ru", "a@x.ru", "c@x.ru"]
unique = list(set(emails))
print(unique) # порядок может отличаться: ['b@x.ru', 'c@x.ru', 'a@x.ru']
Важный нюанс: после set() исходный порядок теряется. Если он нужен, с Python 3.7 порядок вставки сохраняет словарь, и трюк выглядит так:
emails = ["a@x.ru", "b@x.ru", "a@x.ru", "c@x.ru"]
unique = list(dict.fromkeys(emails))
print(unique) # ['a@x.ru', 'b@x.ru', 'c@x.ru'] — порядок сохранён
Зачем это нужно: быстрая проверка вхождения
Вот где множество даёт не удобство, а реальную скорость. Проверка x in collection у списка перебирает элементы по одному — в среднем это O(n). У множества та же проверка — в среднем O(1)1, потому что под капотом хеш-таблица.
На маленьких данных разницы не заметить. На больших она решающая: проверить принадлежность к списку из миллиона элементов может занимать миллисекунды на каждый запрос, к множеству того же размера — микросекунды.
Если вы в цикле проверяетеif item in something, аsomething— это список, почти всегда стоит превратить его в множество один раз перед циклом. Это самая частая бесплатная оптимизация в коде новичка.
banned = {"spam", "ads", "phish"} # множество, а не список
for word in incoming_words:
if word in banned: # O(1) на каждую проверку
block(word)
Операции над множествами
Множества умеют то, ради чего их и придумали в математике: пересечения, объединения, разности. Это читается почти как русский текст.
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(a ^ b) # симметричная разность: {1, 2, 5, 6}
У каждой операции есть метод-синоним: a.union(b), a.intersection(b), a.difference(b). Разница в том, что операторы (|, &) требуют, чтобы справа тоже было множество, а методы принимают любой перебираемый объект — список, кортеж, строку.
Что можно класть в множество, а что нельзя
Вторая частая ошибка: попытка положить в множество список.
ok = {1, "text", (1, 2)} # число, строка, кортеж — работает
fail = {[1, 2], [3, 4]} # TypeError: unhashable type: 'list'
Элемент множества должен быть хешируемым — то есть неизменяемым. Список изменяемый, поэтому его нельзя. А кортеж — можно, потому что он неизменяемый. Это правило одно и то же для ключей словаря.
| Тип | Можно в множество? |
|---|---|
| int, float, str, bool | Да |
| tuple (из хешируемых) | Да |
| frozenset | Да |
| list | Нет |
| dict | Нет |
| set | Нет (но frozenset — да) |
set и frozenset: в чём разница
Обычное множество можно менять: добавлять и удалять элементы. Из-за этого само множество нельзя положить внутрь другого множества. Когда нужно неизменяемое множество — например, как элемент другого множества или ключ словаря — берут frozenset:
a = frozenset({1, 2, 3})
nested = {a, frozenset({4, 5})} # множество из множеств — работает
# a.add(4) # AttributeError: у frozenset нет метода add
Как добавлять и удалять
Для изменяемого множества основные методы:
add(x)— добавить один элемент;update(iterable)— добавить сразу несколько из списка, кортежа, другого множества;discard(x)— удалить элемент, и не упасть, если его нет;remove(x)— удалить элемент, но кинутьKeyError, если его нет;pop()— удалить и вернуть произвольный элемент.
Разница между discard и remove — источник неожиданных падений. Если не уверены, что элемент точно есть, берите discard.
tags = {"python", "web"}
tags.discard("go") # тихо, элемента не было
tags.remove("go") # KeyError: 'go'
Если не получилось
Ошибки, которые вы увидите в консоли, и что они значат:
TypeError: unhashable type: 'list'— вы пытаетесь положить в множество список или словарь. Замените список кортежом, если данные не меняются.KeyError: '...'при удалении — вы вызвалиremove()для элемента, которого нет. Используйтеdiscard().TypeError: 'set' object is not subscriptable— вы обратились к множеству по индексу вродеs[0]. У множества нет порядка и индексов; если он нужен, вам нужен список.- type оказался
dict, а неset— вы создали пустое множество через{}. Толькоset(). - Порядок элементов «прыгает» между запусками — это не баг. Множество не хранит порядок, для стабильного вывода отсортируйте:
sorted(my_set).
Что делать дальше
Разберите множественные включения (set comprehensions) — тот же синтаксис, что у списочных, но в фигурных скобках: {x*x for x in range(10)}. Дальше посмотрите на collections.Counter, когда важно не только наличие элемента, но и сколько раз он встретился — множество это не считает. И держите в голове главный практический вывод: как только видите проверку in по большому списку внутри цикла, замените список множеством.
1 O(1) и O(n) — обозначения из теории сложности. O(1) значит «время не зависит от размера коллекции», O(n) — «растёт пропорционально размеру». Для множества O(1) — это средний случай; при неудачных хешах бывают редкие просадки, но на практике их почти не встретишь.
Субтитры и таймкоды: Расшифровка речи и субтитры по правилам читаемости: 42 знака в строке, две строки, минимум секунда на реплику.
Спросить за 40 ₽Источники: Python docs — Set Types (set, frozenset), Python docs — Sets (tutorial)
Частые вопросы
Чем множество отличается от списка простыми словами?
Почему set() медленнее списка при переборе?
Работает ли это в моей версии Python?
Как сохранить порядок и при этом убрать дубликаты?
Почему падает с ошибкой unhashable type?
Как получить один элемент из множества?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.