Posted on July 27, 2018· Updated on July 3, 2022

Множества Python: Что, Зачем и Как - Шпаргалка по Python

#python #basics
Image for Множества Python: Что, Зачем и Как - Шпаргалка по Python

Python оснащен несколькими встроенными типами данных, которые помогают нам организовывать наши данные. Эти структуры включают lists (списки), dictionaries (словари), tuples (кортежи) и sets (множества).

Из документации Python 3

set — это неупорядоченная коллекция без повторяющихся элементов. Основные варианты использования включают проверку членства и удаление повторяющихся записей. Объекты множеств также поддерживают математические операции, такие как объединение (union), пересечение (intersection), разность (difference) и симметричная разность (symmetric difference).

В этой статье мы рассмотрим каждый из элементов, перечисленных в приведенном выше определении. Давайте начнем прямо сейчас и посмотрим, как мы можем их создать.

Инициализация множества (Initializing a Set)

Существует два способа создания множества: один — использовать встроенную функцию set() и передать list элементов, а другой — использовать фигурные скобки {}.

Инициализация множества с помощью встроенной функции set()

>>> s1 = set([1, 2, 3])
>>> s1
{1, 2, 3}
>>> type(s1)
<class 'set'>

Инициализация множества с помощью фигурных скобок {}

>>> s2 = {3, 4, 5}
>>> s2
{3, 4, 5}
>>> type(s2)
<class 'set'>
>>>

Пустые множества (Empty Sets)

При создании множества убедитесь, что вы не используете пустые фигурные скобки {}, иначе вы получите пустой словарь.

>>> s = {}
>>> type(s)
<class 'dict'>

Сейчас подходящий момент упомянуть, что для простоты все примеры, приведенные в этой статье, будут использовать целые числа из одной цифры, но множества могут содержать все хешируемые типы данных, которые поддерживает Python. Другими словами, целые числа, строки и кортежи, но не изменяемые элементы, такие как списки или словари:

>>> s = {1, 'coffee', [4, 'python']}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

Теперь, когда вы знаете, как создать множество и какие элементы оно может содержать, давайте продолжим и посмотрим, почему оно всегда должно быть в нашем арсенале.

Почему их следует использовать (Why you should Use them)

Мы можем писать код не одним, а несколькими способами. Некоторые считаются довольно плохими, а другие — понятными, лаконичными и поддерживаемыми. Или “pythonic”.

Из The Hitchhiker’s Guide to Python

Когда опытный разработчик Python (Pythonista) называет части кода не «Pythonic», он обычно имеет в виду, что эти строки кода не соответствуют общим рекомендациям и не выражают свое намерение в том, что считается лучшим (читай: наиболее читаемым) способом.

Давайте начнем изучать, как множества Python могут помочь нам не только с читаемостью, но и со временем выполнения нашей программы.

Неупорядоченная коллекция элементов (Unordered collection of elements)

Прежде всего: вы не можете получить доступ к объекту set по индексам.

>>> s = {1, 2, 3}
>>> s[0]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'set' object does not support indexing

Или изменять их с помощью срезов:

>>> s[0:2]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'set' object is not subscriptable

НО, если нам нужно удалить дубликаты или выполнить математические операции, такие как объединение списков (объединение), мы можем и ВСЕГДА ДОЛЖНЫ использовать множества.

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

Отсутствие повторяющихся элементов (No duplicate items)

Пиша это, я не могу перестать думать о том, сколько раз я использовал цикл for и оператор if для проверки и удаления повторяющихся элементов в списке. Мое лицо краснеет, когда я вспоминаю, что не раз писал что-то вроде этого:

>>> my_list = [1, 2, 3, 2, 3, 4]
>>> no_duplicate_list = []
>>> for item in my_list:
...     if item not in no_duplicate_list:
...             no_duplicate_list.append(item)
...
>>> no_duplicate_list
[1, 2, 3, 4]

Или использовал списковое включение (list comprehension):

>>> my_list = [1, 2, 3, 2, 3, 4]
>>> no_duplicate_list = []
>>> [no_duplicate_list.append(item) for item in my_list if item not in no_duplicate_list]
[None, None, None, None]
>>> no_duplicate_list
[1, 2, 3, 4]

Но это нормально, все это больше не имеет значения, потому что теперь у нас есть множества:

>>> my_list = [1, 2, 3, 2, 3, 4]
>>> no_duplicate_list = list(set(my_list))
>>> no_duplicate_list
[1, 2, 3, 4]

Производительность множеств (Sets performance)

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

>>> from timeit import timeit
>>> def no_duplicates(list):
...     no_duplicate_list = []
...     [no_duplicate_list.append(item) for item in list if item not in no_duplicate_list]
...     return no_duplicate_list
...
>>> # сначала посмотрим, как работает список:
>>> print(timeit('no_duplicates([1, 2, 3, 1, 7])', globals=globals(), number=1000))
0.0018683355819786227
>>> from timeit import timeit
>>> # а теперь множество:
>>> print(timeit('list(set([1, 2, 3, 1, 2, 3, 4]))', number=1000))
0.0010220493243764395
>>> # быстрее и чище =)

С помощью множеств мы не только пишем меньше строк кода, чем с использованием списковых включений, но и получаем более читаемый и производительный код.

помните, что множества неупорядочены

Нет гарантии, что при преобразовании их обратно в список порядок элементов будет сохранен.

Из Дзена Python:

Красивое лучше, чем уродливое.
Явное лучше, чем неявное.
Простое лучше, чем сложное.
Плоское лучше, чем вложенное.

Разве множества не являются Красивыми, Явными, Простыми и Плоскими?

Проверка членства (Membership tests)

Каждый раз, когда мы используем оператор if для проверки, находится ли элемент, например, в списке, мы выполняем проверку членства:

my_list = [1, 2, 3]
>>> if 2 in my_list:
...     print('Yes, this is a membership test!')
...
# Yes, this is a membership test!

И множества более производительны, чем списки при их выполнении:

>>> from timeit import timeit
>>> def in_test(iterable):
...     for i in range(1000):
...             if i in iterable:
...                     pass
...
>>> timeit('in_test(iterable)', setup="from __main__ import in_test; iterable = list(range(1000))", number=1000)
# 12.459663048726043
>>> from timeit import timeit
>>> def in_test(iterable):
...     for i in range(1000):
...             if i in iterable:
...                     pass
...
>>> timeit('in_test(iterable)', setup="from __main__ import in_test; iterable = set(range(1000))", number=1000)
# 0.12354438152988223

Приведенные выше тесты взяты из этой ветки Stack Overflow.

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

Добавление элементов (Adding Elements)

В зависимости от количества добавляемых элементов нам придется выбирать между методами add() и update().

add() добавит один элемент:

>>> s = {1, 2, 3}
>>> s.add(4)
>>> s
{1, 2, 3, 4}

А update() — несколько:

>>> s = {1, 2, 3}
>>> s.update([2, 3, 4, 5, 6])
>>> s
{1, 2, 3, 4, 5, 6}

Помните, множества удаляют дубликаты.

Удаление элементов (Removing Elements)

Если вы хотите получить уведомление, когда ваш код пытается удалить элемент, которого нет в множестве, используйте remove(). В противном случае discard() предоставляет подходящую альтернативу:

>>> s = {1, 2, 3}
>>> s.remove(3)
>>> s
{1, 2}
>>> s.remove(3)
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# KeyError: 3

discard() не вызовет ошибок:

>>> s = {1, 2, 3}
>>> s.discard(3)
>>> s
{1, 2}
>>> s.discard(3)
>>> # ничего не происходит!

Мы также можем использовать pop() для случайного удаления элемента:

>>> s = {1, 2, 3, 4, 5}
>>> s.pop()  # удаляет произвольный элемент
1
>>> s
{2, 3, 4, 5}

Или clear() для удаления всех значений из множества:

>>> s = {1, 2, 3, 4, 5}
>>> s.clear()  # отбросить все элементы
>>> s
set()

Метод union()

union() или | создаст новое множество, содержащее все элементы из предоставленных нами множеств:

>>> s1 = {1, 2, 3}
>>> s2 = {3, 4, 5}
>>> s1.union(s2)  # или 's1 | s2'
{1, 2, 3, 4, 5}

Метод intersection()

intersection или & вернет множество, содержащее только те элементы, которые являются общими для всех них:

>>> s1 = {1, 2, 3}
>>> s2 = {2, 3, 4}
>>> s3 = {3, 4, 5}
>>> s1.intersection(s2, s3)  # или 's1 & s2 & s3'
{3}

Метод difference()

Разность создает новое множество со значениями, которые есть в “s1”, но отсутствуют в “s2”:

>>> s1 = {1, 2, 3}
>>> s2 = {2, 3, 4}
>>> s1.difference(s2)  # или 's1 - s2'
{1}

symmetric_difference()

symmetric_difference или ^ вернет все значения, которые не являются общими для множеств.

>>> s1 = {1, 2, 3}
>>> s2 = {2, 3, 4}
>>> s1.symmetric_difference(s2)  # или 's1 ^ s2'
{1, 4}

Заключение (Conclusions)

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