Множества в Python

Множество (set) в Python решает две конкретные задачи: быстрая проверка членства (есть ли элемент в коллекции) и хранение только уникальных значений без дубликатов. Это неупорядоченная коллекция, основанная на математической концепции множества.

Что такое множество?

Множество в Python — это неупорядоченная коллекция уникальных элементов. Два ключевых свойства множеств:

  1. Неупорядоченность: элементы не имеют определённого порядка и не индексируются — обратиться к «третьему элементу множества» нельзя, можно только перебрать все элементы или проверить, есть ли в нём конкретный
  2. Уникальность: каждый элемент встречается только один раз

Основные характеристики множеств:

  • Изменяемость: можно добавлять и удалять элементы
  • Неизменяемые элементы: внутрь множества можно положить только неизменяемые объекты (числа, строки, кортежи)
  • Эффективность: оптимизированы для быстрой проверки вхождения элементов

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

Создание множеств

С помощью фигурных скобок

Python 3.13
# Множество целых чисел
numbers = {1, 2, 3, 4, 5}
print(numbers)
{1, 2, 3, 4, 5}
# Автоматическое удаление дубликатов
duplicates = {1, 2, 2, 3, 3, 3, 4, 5, 5}
print(duplicates)
{1, 2, 3, 4, 5}
# В одном множестве могут лежать разные неизменяемые типы
mixed = {1, "привет", (1, 2, 3)}
print(len(mixed))  # число, строка и кортеж — все три поместились
3

С помощью конструктора set()

Python 3.13
# Пустое множество
empty_set = set()
print(empty_set)
set()
# Создание множества из списка
numbers_set = set([1, 2, 2, 3, 4, 4, 5])
print(numbers_set)
{1, 2, 3, 4, 5}
# Создание множества из строки — повторяющиеся буквы схлопываются
letters = set("hello")
print(len(letters))  # в "hello" две 'l', в множестве осталась одна — всего 4 буквы
4

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

Проверка наличия элемента

Python 3.13
fruits = {"яблоко", "банан", "вишня"}

print("яблоко" in fruits)
True
print("груша" in fruits)
False

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

Порядок элементов в множестве произвольный, поэтому в примерах со строками ниже мы выводим их через sorted(): эта функция возвращает отсортированный список, и вывод не скачет от запуска к запуску.

Python 3.13
fruits = {"яблоко", "банан"}

# Добавление одного элемента
fruits.add("вишня")
print(sorted(fruits))
['банан', 'вишня', 'яблоко']
# Добавление нескольких элементов
fruits.update(["груша", "апельсин"])
print(sorted(fruits))
['апельсин', 'банан', 'вишня', 'груша', 'яблоко']
# Удаление элемента
fruits.remove("банан")  # вызывает KeyError, если элемента нет
print(sorted(fruits))
['апельсин', 'вишня', 'груша', 'яблоко']
# Безопасное удаление элемента
fruits.discard("вишня")  # не вызывает ошибку, если элемента нет
print(sorted(fruits))
['апельсин', 'груша', 'яблоко']
# pop() убирает и возвращает какой-то элемент — какой именно, заранее неизвестно
removed = fruits.pop()
print(len(fruits))  # осталось на один меньше
2
# Очистка множества
fruits.clear()
print(fruits)
set()

Перебор множества

Множество перебирают циклом for. Порядок при этом произвольный и может меняться от запуска к запуску — это и значит «неупорядоченное». Когда нужен предсказуемый порядок, сортируем через sorted():

Python 3.13
colors = {"красный", "синий", "зеленый"}

for color in sorted(colors):
    print(color)
зеленый
красный
синий

Математические операции над множествами

Три основные операции — объединение, пересечение и разность. Удобно визуализировать их диаграммами Венна:

Диаграммы Венна для трёх операций над множествами: объединение A | B, пересечение A & B, разность A - B

Объединение (Union)

Все элементы из обоих множеств:

Python 3.13
a = {1, 2, 3}
b = {3, 4, 5}

union_set = a | b
print(union_set)
{1, 2, 3, 4, 5}

То же самое можно написать как a.union(b).

Пересечение (Intersection)

Элементы, которые есть в обоих множествах:

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

intersection_set = a & b
print(intersection_set)
{3, 4}

То же самое можно написать как a.intersection(b).

Разность (Difference)

Элементы первого множества, которых нет во втором:

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

difference_set = a - b
print(difference_set)
{1, 2}

То же самое можно написать как a.difference(b).

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

Python 3.13
a = {1, 2, 3}
b = {1, 2, 3, 4, 5}
c = {1, 2, 3}

# Равенство множеств
print(a == c)  # Содержат одинаковые элементы
True
# Подмножества
print(a.issubset(b))  # Все элементы a есть в b
True
print(a < b)  # a является строгим подмножеством b
True
# Надмножества
print(b.issuperset(a))  # b содержит все элементы a
True
print(b > a)  # b является строгим надмножеством a
True
# Проверка на отсутствие общих элементов
d = {6, 7, 8}
print(a.isdisjoint(d))  # Нет общих элементов
True

Неизменяемые множества (frozenset)

Если нужен неизменяемый вариант множества, используйте frozenset:

Python 3.13
# Создание frozenset
immutable_set = frozenset([1, 2, 3, 4])
print(immutable_set)
frozenset({1, 2, 3, 4})
# Попытка изменить frozenset вызывает ошибку
try:
    immutable_set.add(5)
except AttributeError as e:
    print(f"Ошибка: {e}")
Ошибка: 'frozenset' object has no attribute 'add'
# frozenset можно использовать как ключ словаря или элемент другого множества
normal_set = {frozenset([1, 2]), frozenset([3, 4])}
print(len(normal_set))  # два frozenset'а поместились внутрь
2

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

1. Удаление дубликатов из списка

Python 3.13
numbers = [1, 2, 2, 3, 3, 3, 4, 5, 5]
unique_numbers = list(set(numbers))
print(unique_numbers)
[1, 2, 3, 4, 5]

2. Нахождение общих элементов

Python 3.13
users_group1 = ["Анна", "Иван", "Мария", "Петр", "Елена"]
users_group2 = ["Иван", "Ольга", "Елена", "Алексей"]

# Общие элементы (пересечение)
common_users = set(users_group1) & set(users_group2)
print(f"Пользователи в обеих группах: {sorted(common_users)}")
Пользователи в обеих группах: ['Елена', 'Иван']
# Элементы только из первой группы (разность)
only_group1 = set(users_group1) - set(users_group2)
print(f"Только в группе 1: {sorted(only_group1)}")
Только в группе 1: ['Анна', 'Мария', 'Петр']
# Все уникальные элементы (объединение)
all_users = set(users_group1) | set(users_group2)
print(f"Все уникальные пользователи: {sorted(all_users)}")
Все уникальные пользователи: ['Алексей', 'Анна', 'Елена', 'Иван', 'Мария', 'Ольга', 'Петр']

3. Проверка уникальности элементов

Python 3.13
def are_all_unique(items):
    """Проверяет, все ли элементы в последовательности уникальны."""
    return len(set(items)) == len(items)

print(are_all_unique([1, 2, 3, 4, 5]))
True
print(are_all_unique([1, 2, 3, 3, 4]))
False

Ограничения и производительность

Ограничения

Элементы множества должны быть хешируемыми (неизменяемыми):

Python 3.13
# Работает с неизменяемыми типами данных
valid_set = {1, "hello", (1, 2, 3)}
print(len(valid_set))  # число, строка и кортеж хешируемы — все три поместились
3
# Ошибка с изменяемыми типами данных
try:
    invalid_set = {1, [2, 3], {"a": 1}}
except TypeError as e:
    print(f"Ошибка: {e}")
Ошибка: unhashable type: 'list'

Можно добавлять:

  • Числа (int, float, complex)
  • Строки (str)
  • Кортежи (tuple) с хешируемыми элементами
  • Frozenset

Нельзя добавлять:

  • Списки (list)
  • Словари (dict)
  • Множества (set)

Производительность

Ради быстрого поиска множества и придуманы. Проверим на миллионе чисел: ищем последнее — то есть худший случай для списка, который вынужден просмотреть всё подряд.

Python 3.13
import time

data = list(range(1_000_000))
data_set = set(data)

start = time.time()
for _ in range(100):
    999_999 in data
list_time = time.time() - start

start = time.time()
for _ in range(100):
    999_999 in data_set
set_time = time.time() - start

print(f"Поиск в списке: {list_time:.3f} сек")
Поиск в списке: 0.442 сек
print(f"Поиск в множестве: {set_time:.5f} сек")
Поиск в множестве: 0.00001 сек

Конкретные числа у вас получатся свои — они зависят от машины и загруженности, — но разрыв никуда не денется: десятки тысяч раз. Списку приходится перебирать элементы по одному, пока не найдёт нужный. Множество вместо перебора сразу вычисляет, где значение должно лежать, и проверяет только это место — и делает это одинаково быстро хоть на десяти элементах, хоть на миллионе.

Операции со сложностью O(1) (константное время):

  • Проверка наличия элемента: x in set
  • Добавление элемента: set.add(x)
  • Удаление элемента: set.remove(x), set.discard(x)

Проверка понимания

Что выведет print(set([1, 2, 2, 3, 3, 3]))?