2.10. Множества¶
Множество — это неупорядоченная коллекция уникальных элементов. Добавление значения, которое уже присутствует, не имеет эффекта; итерация выдаёт каждое значение ровно один раз. Множества — правильный инструмент, когда важны принадлежность и устранение дубликатов, а порядок — нет.
2.10.1. Создание множества¶
Используйте фигурные скобки для непустого множества или set() для пустого:
colours = {"red", "green", "blue"}
empty = set()
Фигурные скобки выглядят как литерал dict; {} сам по себе — это пустой словарь, а не пустое множество — одна из исторических случайностей Python. Используйте set() для пустого случая.
set() также строит множество из любого итерируемого объекта, что является стандартным способом удалить дубликаты из последовательности:
nums = [1, 2, 2, 3, 1, 4]
unique = set(nums)
print(unique)
Вывод:
{1, 2, 3, 4}
Порядок вывода может различаться — множества не обещают итерировать в каком-либо определённом порядке.
2.10.2. Множество против словаря¶
И множества, и словари хранят уникальные элементы в хеш-таблице. Различие — в том, что несёт с собой каждый элемент:
dictхранит пары ключ-значение. Поиск ключа возвращает его значение.setхранит только элементы. Поиск элемента сообщает вам, присутствует ли он.
Выбор между этими двумя зависит от того, означает ли что-нибудь значение рядом с каждым элементом:
Выбирайте множество, когда рядом с каждым элементом не должно быть никакого значения — вас интересует только, присутствует ли элемент, или вы объединяете группы уникальных элементов с помощью объединения / пересечения.
Выбирайте словарь, когда каждый элемент сопоставлен с данными, которые поиск должен извлекать — отображение конфигурации, кеш, счётчик с ключом по имени.
Эти два типа разделяют большую часть внешнего синтаксиса, откуда и происходит большая часть путаницы. Различия в одном блоке:
set |
dict |
|
|---|---|---|
содержит |
уникальные элементы |
уникальные ключи, каждый со значением |
заполненный литерал |
|
|
пустой литерал |
|
|
проверка принадлежности |
|
|
получить значение |
н/д |
|
добавить элемент |
|
|
итерация |
выдаёт элементы |
выдаёт ключи (используйте |
Асимметрия между заполненными и пустыми литералами — это подводный камень, о котором стоит сказать:
Фигурные скобки с элементами внутри —
{1, 2, 3}— это литерал множества; фигурные скобки с парами ключ-значение —{"a": 1}— это литерал словаря. Парсер различает их по тому, что находится внутри.Фигурные скобки с пустым содержимым —
{}— это пустой словарь, а не пустое множество. Словари появились первыми; пустой литерал принадлежит им. У пустого множества вообще нет литерала со скобками, и оно должно записываться какset().
Распространённый приём, когда у словаря когда-либо читаются только ключи, — переключиться на множество: это делает намерение очевидным и убирает неиспользуемые значения из памяти.
2.10.3. Добавление и удаление¶
set.add()— вставить один элемент.set.discard()— удалить элемент, если он присутствует, ничего не делать, если его нет.set.remove()— удалить элемент; вызватьKeyError, если он отсутствует.set.clear()— опустошить множество.
s = {1, 2, 3}
s.add(4)
s.discard(99) # silent: 99 not in s
s.remove(2)
print(s)
Вывод:
{1, 3, 4}
2.10.4. Принадлежность¶
Оператор in проверяет принадлежность. Для множества это занимает примерно постоянное время независимо от размера — что является основной причиной выбрать множество вместо list, когда вам нужно только спросить «есть ли это значение там»:
if "red" in colours:
print("colour is allowed")
list с тем же содержимым каждый раз сканировался бы с начала, что приемлемо для десяти элементов, но медленно для десяти тысяч.
2.10.5. Операции над множествами¶
Два множества можно объединять с помощью обычных математических операций. Каждая имеет как форму оператора, так и форму метода:
a | bилиa.union(b)— всё, что есть в любом из множеств.a & bилиa.intersection(b)— только то, что встречается в обоих.a - bилиa.difference(b)— то, что есть вa, но не вb.a ^ bилиa.symmetric_difference(b)— то, что есть в одном, но не в обоих.
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
print(a | b)
print(a & b)
print(a - b)
print(a ^ b)
Вывод:
{1, 2, 3, 4, 5, 6}
{3, 4}
{1, 2}
{1, 2, 5, 6}
Формы операторов доступны только для чтения; формы методов принимают любой итерируемый объект справа, а не только другое множество (a.union([5, 6])). Выбирайте ту, которая лучше читается в контексте.
2.10.6. Что может входить в множество¶
Элементы множества должны быть хешируемыми — то же ограничение, что и для ключей dict. int, float, str, bool, bytes и tuple (когда его содержимое само по себе хешируемо) — все работают. list и dict — нет; попытка добавить один из них вызывает TypeError.
2.10.7. frozenset¶
Обычное set является изменяемым: каждый вызов add / remove / discard меняет объект на месте. Эта изменяемость лишает его права быть хешируемым, поэтому множество не может использоваться как ключ dict или как член другого множества.
frozenset — это неизменяемый аналог. У него те же поиски и операторы (in, |, &, -, ^), что и у set, но нет add / remove и нет методов, которые изменяют его. Поскольку его содержимое никогда не может измениться, хеш frozenset чётко определён — поэтому он является хешируемым:
primary = frozenset({"red", "green", "blue"})
secondary = frozenset({"yellow", "purple", "orange"})
palettes = {
primary: "RGB",
secondary: "mixed",
}
print(palettes[primary])
Вывод:
RGB
Создавайте frozenset из любого итерируемого объекта — frozenset() для пустого случая, frozenset(some_set), чтобы сделать неизменяемый снимок существующего множества:
snapshot = frozenset(s) # immutable copy of s
s.add("new") # snapshot does not change
Две распространённые причины обратиться к нему:
Использование как ключа словаря или члена множества. Везде, где одно значение не может охватить то, что вам нужно, может подойти
frozensetзначений — «набор признаков, поддерживаемых этим драйвером», «набор выводов, которые использует этот профиль».Фиксация константы.
frozensetдопустимых имён на уровне модуля не может быть случайно изменён вызывающим кодом; обычноеsetможет. Предпочитайтеfrozensetдля всего, что должно быть доступно только для чтения после создания.