2.10. Zbiory¶
Zbiór to nieuporządkowana kolekcja unikalnych elementów. Dodanie wartości, która już występuje, nie ma żadnego efektu; iteracja zwraca każdą wartość dokładnie raz. Zbiory są właściwym narzędziem, gdy liczy się przynależność i usuwanie duplikatów, a kolejność nie ma znaczenia.
2.10.1. Tworzenie zbioru¶
Użyj nawiasów klamrowych dla niepustego zbioru lub set() dla pustego:
colours = {"red", "green", "blue"}
empty = set()
Nawiasy klamrowe wyglądają jak literał dict; samo {} to pusty słownik, a nie pusty zbiór – jeden z historycznych wypadków Pythona. Dla przypadku pustego użyj set().
set() buduje też zbiór z dowolnego obiektu iterowalnego, co jest standardowym sposobem usuwania duplikatów z sekwencji:
nums = [1, 2, 2, 3, 1, 4]
unique = set(nums)
print(unique)
Wynik:
{1, 2, 3, 4}
Kolejność wypisywania może być różna – zbiory nie obiecują iterowania w żadnym określonym porządku.
2.10.2. Zbiór a słownik¶
Zarówno zbiory, jak i słowniki przechowują unikalne elementy w tablicy mieszającej. Różnica polega na tym, co każdy element ze sobą niesie:
dictprzechowuje pary klucz-wartość. Wyszukanie klucza zwraca jego wartość.setprzechowuje tylko elementy. Wyszukanie elementu mówi, czy się on tam znajduje.
Wybór pomiędzy nimi zależy od tego, czy wartość towarzysząca każdemu elementowi cokolwiek oznacza:
Sięgnij po zbiór, gdy do każdego elementu nie należy żadna wartość – interesuje cię tylko, czy element jest obecny, lub łączysz grupy unikalnych elementów za pomocą sumy / przecięcia.
Sięgnij po słownik, gdy każdy element jest sparowany z danymi, które wyszukiwanie ma pobierać – mapa konfiguracji, pamięć podręczna, licznik kluczowany nazwą.
Oba typy dzielą wiele składni na powierzchni, co jest źródłem większości nieporozumień. Różnice w jednym bloku:
set |
dict |
|
|---|---|---|
przechowuje |
unikalne elementy |
unikalne klucze, każdy z wartością |
literał wypełniony |
|
|
literał pusty |
|
|
test przynależności |
|
|
pobranie wartości |
nie dotyczy |
|
dodanie elementu |
|
|
iterowanie |
zwraca elementy |
zwraca klucze (dla par użyj |
Asymetria między literałem wypełnionym a pustym jest pułapką wartą podkreślenia:
Nawiasy klamrowe z elementami w środku –
{1, 2, 3}– to literał zbioru; nawiasy klamrowe z parami klucz-wartość –{"a": 1}– to literał słownika. Parser rozróżnia je po tym, co znajduje się w środku.Nawiasy klamrowe z niczym w środku –
{}– to pusty słownik, a nie pusty zbiór. Słowniki pojawiły się pierwsze; pusty literał należy do nich. Pusty zbiór nie ma żadnego literału z nawiasami klamrowymi i musi być zapisany jakoset().
Częstym wzorcem, gdy odczytywane są tylko klucze słownika, jest przejście na zbiór – czyni to intencję oczywistą i usuwa nieużywane wartości z pamięci.
2.10.3. Dodawanie i usuwanie¶
set.add()– wstawia jeden element.set.discard()– usuwa element, jeśli jest obecny, i nic nie robi, jeśli go nie ma.set.remove()– usuwa element; zgłaszaKeyError, jeśli go brak.set.clear()– opróżnia zbiór.
s = {1, 2, 3}
s.add(4)
s.discard(99) # silent: 99 not in s
s.remove(2)
print(s)
Wynik:
{1, 3, 4}
2.10.4. Przynależność¶
Operator in sprawdza przynależność. Dla zbioru działa w przybliżeniu w stałym czasie niezależnie od rozmiaru – co jest głównym powodem, by wybrać zbiór zamiast list, gdy potrzebujesz tylko zapytać „czy ta wartość tam jest”:
if "red" in colours:
print("colour is allowed")
list o tej samej zawartości za każdym razem skanowałaby od początku, co jest w porządku dla dziesięciu elementów, ale wolne dla dziesięciu tysięcy.
2.10.5. Operacje na zbiorach¶
Dwa zbiory można łączyć za pomocą zwykłych operacji matematycznych. Każda z nich ma zarówno postać operatorową, jak i metodową:
a | bluba.union(b)– wszystko, co jest w którymkolwiek ze zbiorów.a & bluba.intersection(b)– tylko to, co pojawia się w obu.a - bluba.difference(b)– wa, ale nie wb.a ^ bluba.symmetric_difference(b)– w jednym, ale nie w obu.
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
print(a | b)
print(a & b)
print(a - b)
print(a ^ b)
Wynik:
{1, 2, 3, 4, 5, 6}
{3, 4}
{1, 2}
{1, 2, 5, 6}
Postacie operatorowe są tylko do odczytu; postacie metodowe przyjmują po prawej stronie dowolny obiekt iterowalny, a nie tylko inny zbiór (a.union([5, 6])). Wybierz tę, która lepiej czyta się w danym kontekście.
2.10.6. Co może trafić do zbioru¶
Elementy zbioru muszą być haszowalne – to samo ograniczenie, co dla kluczy dict. int, float, str, bool, bytes oraz tuple (gdy jej zawartość sama jest haszowalna) – wszystkie działają. list i dict nie; próba dodania jednego z nich zgłasza TypeError.
2.10.7. frozenset¶
Zwykły set jest zmienny: każde wywołanie add / remove / discard zmienia obiekt w miejscu. Ta zmienność dyskwalifikuje go z bycia haszowalnym, więc zbiór nie może być użyty jako klucz dict ani jako element innego zbioru.
frozenset to niezmienny odpowiednik. Ma te same wyszukiwania i operatory (in, |, &, -, ^) co set, ale nie ma add / remove ani żadnych metod, które go modyfikują. Ponieważ nic nigdy nie może zmienić jego zawartości, hasz frozenset jest dobrze określony – więc jest haszowalny:
primary = frozenset({"red", "green", "blue"})
secondary = frozenset({"yellow", "purple", "orange"})
palettes = {
primary: "RGB",
secondary: "mixed",
}
print(palettes[primary])
Wynik:
RGB
Skonstruuj frozenset z dowolnego obiektu iterowalnego – frozenset() dla przypadku pustego, frozenset(some_set), aby wziąć niezmienny zrzut istniejącego zbioru:
snapshot = frozenset(s) # immutable copy of s
s.add("new") # snapshot does not change
Dwa typowe powody, by po niego sięgnąć:
Użycie jako klucz słownika lub element zbioru. Wszędzie tam, gdzie pojedyncza wartość nie potrafi uchwycić tego, czego potrzebujesz, może to zrobić
frozensetwartości – „zbiór funkcji obsługiwanych przez ten sterownik”, „zbiór pinów używanych przez ten profil”.Zabezpieczenie stałej.
frozensetna poziomie modułu zawierający dozwolone nazwy nie może zostać przypadkowo zmodyfikowany przez wywołującego; zwykłysetmoże. Preferujfrozensetdla wszystkiego, co po skonstruowaniu ma być tylko do odczytu.