2.10. Множини¶
Множина — це невпорядкована колекція унікальних елементів. Додавання вже наявного значення не дає жодного ефекту; ітерація повертає кожне значення рівно один раз. Множини — правильний інструмент, коли важлива перевірка наявності та усунення дублікатів, а порядок — ні.
2.10.1. Створення множини¶
Використовуйте фігурні дужки для непорожньої множини або set() для порожньої:
colours = {"red", "green", "blue"}
empty = set()
Фігурні дужки схожі на літерал dict; {} само по собі є порожнім dict, а не порожньою множиною — один із класичних казусів Python. Використовуйте set() для порожнього випадку.
set() також будує множину з будь-якого ітерованого об’єкта — це стандартний спосіб видалити дублікати з послідовності:
nums = [1, 2, 2, 3, 1, 4]
unique = set(nums)
print(unique)
Output:
{1, 2, 3, 4}
Порядок виведення може відрізнятися — множини не гарантують жодного конкретного порядку ітерації.
2.10.2. Множина vs словник¶
Множини та словники зберігають унікальні елементи в хеш-таблиці. Різниця в тому, що кожен елемент несе з собою:
dictзберігає пари ключ-значення. Пошук за ключем повертає його значення.setзберігає лише елементи. Пошук елемента повідомляє лише про те, чи є він там.
Вибір між ними залежить від того, чи має значення дані поруч із кожним елементом:
Обирайте множину, коли жодне значення не потрібно поруч із кожним елементом — вам важливо лише, чи присутній елемент, або ви комбінуєте групи унікальних елементів за допомогою об’єднання / перетину.
Обирайте словник, коли кожен елемент пов’язаний з даними, які потрібно отримати при пошуку — конфігурація, кеш, лічильник за ключем-іменем.
Обидва типи мають схожий синтаксис, що й є основним джерелом плутанини. Відмінності в одному блоці:
set |
dict |
|
|---|---|---|
зберігає |
унікальні елементи |
унікальні ключі, кожен з відповідним значенням |
непорожній літерал |
|
|
порожній літерал |
|
|
перевірка наявності |
|
|
отримати значення |
n/a |
|
додати елемент |
|
|
ітерувати |
повертає елементи |
повертає ключі (використовуйте |
Асиметрія між непорожнім і порожнім літералами — ось на що варто звернути увагу:
Фігурні дужки з елементами всередині —
{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)
Output:
{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)
Output:
{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])
Output:
RGB
Конструюйте frozenset з будь-якого ітерованого об’єкта — frozenset() для порожнього випадку, frozenset(some_set) для створення незмінного знімку існуючої множини:
snapshot = frozenset(s) # immutable copy of s
s.add("new") # snapshot does not change
Дві поширені причини звернутися до нього:
Використання як ключа словника або елемента множини. Коли одне значення не може відобразити те, що вам потрібно,
frozensetзначень може — «множина ознак, підтримуваних цим драйвером», «множина виводів, які використовує цей профіль».Зафіксувати константу.
frozensetна рівні модуля з допустимими іменами не може бути випадково змінений кодом, що його викликає; звичайнаset— може. Надавайте перевагуfrozensetдля всього, що має бути доступним лише для читання після створення.