2.10. Sets¶
Een set is een ongeordende verzameling van unieke items. Een waarde toevoegen die al aanwezig is heeft geen effect; iteratie levert elke waarde precies één keer op. Sets zijn het juiste hulpmiddel wanneer lidmaatschap en ontdubbeling belangrijk zijn en volgorde niet.
2.10.1. Een set maken¶
Gebruik accolades voor een niet-lege set, of set() voor een lege:
colours = {"red", "green", "blue"}
empty = set()
De accolades zien eruit als een dict-literal; {} op zichzelf is een lege dict, geen lege set – een van Pythons historische toevalligheden. Gebruik set() voor het lege geval.
set() bouwt ook een set uit elke iterabele, wat de standaardmanier is om duplicaten uit een reeks te verwijderen:
nums = [1, 2, 2, 3, 1, 4]
unique = set(nums)
print(unique)
Uitvoer:
{1, 2, 3, 4}
De printvolgorde kan variëren – sets garanderen geen iteratie in een bepaalde volgorde.
2.10.2. Set versus dict¶
Sets en dicts slaan beide unieke items op in een hashtabel. Wat elk item met zich meedraagt is het verschil:
Een
dictslaat sleutel-waardeparen op. Het opzoeken van een sleutel retourneert de bijbehorende waarde.Een
setslaat alleen de items op. Het opzoeken van een item vertelt je of het aanwezig is.
De keuze tussen de twee gaat over de vraag of de waarde naast elk item iets betekent:
Grijp naar een set wanneer er geen waarde naast elk item hoort – je geeft er alleen om of het item aanwezig is, of je combineert groepen unieke items met vereniging / doorsnede.
Grijp naar een dict wanneer elk item gekoppeld is aan gegevens die de opzoeking moet ophalen – een configuratiekaart, een cache, een teller geïndexeerd op naam.
De twee typen delen veel oppervlakkige syntaxis, en daar komt de meeste verwarring vandaan. De verschillen in één blok:
set |
dict |
|
|---|---|---|
bevat |
unieke items |
unieke sleutels, elk met een waarde |
gevulde literal |
|
|
lege literal |
|
|
lidmaatschapstest |
|
|
een waarde ophalen |
n.v.t. |
|
een item toevoegen |
|
|
itereren |
levert items op |
levert sleutels op (gebruik |
De asymmetrie tussen de gevulde en de lege literals is de valkuil die het waard is om te benoemen:
Accolades met items erin –
{1, 2, 3}– vormen een set-literal; accolades met sleutel-waardeparen –{"a": 1}– vormen een dict-literal. De parser onderscheidt ze op basis van wat erbinnen staat.Accolades met niets erbinnen –
{}– vormen een lege dict, geen lege set. Dicts kwamen eerst; de lege literal hoort bij hen. Een lege set heeft helemaal geen accoladeliteral en moet worden geschreven alsset().
Een veelvoorkomend patroon, wanneer alleen de sleutels van een dict ooit worden gelezen, is om over te schakelen naar een set – dat maakt de bedoeling duidelijk en snijdt de ongebruikte waarden uit het geheugen weg.
2.10.3. Toevoegen en verwijderen¶
set.add()– voegt één item in.set.discard()– verwijdert een item als het aanwezig is, doet niets als dat niet zo is.set.remove()– verwijdert een item; veroorzaakt eenKeyErrorals het ontbreekt.set.clear()– leegt de set.
s = {1, 2, 3}
s.add(4)
s.discard(99) # silent: 99 not in s
s.remove(2)
print(s)
Uitvoer:
{1, 3, 4}
2.10.4. Lidmaatschap¶
De in-operator test op lidmaatschap. Bij een set verloopt dit ongeveer in constante tijd, ongeacht de grootte – wat de belangrijkste reden is om een set te kiezen boven een list wanneer je alleen hoeft te vragen “zit deze waarde erin”:
if "red" in colours:
print("colour is allowed")
Een list met dezelfde inhoud zou elke keer vanaf het begin scannen, wat prima is voor tien items maar traag voor tienduizend.
2.10.5. Setbewerkingen¶
Twee sets kunnen worden gecombineerd met de gebruikelijke wiskundige bewerkingen. Elke bewerking heeft zowel een operatorvorm als een methodevorm:
a | bofa.union(b)– alles in een van beide sets.a & bofa.intersection(b)– alleen wat in beide voorkomt.a - bofa.difference(b)– wat inazit maar niet inb.a ^ bofa.symmetric_difference(b)– wat in een van beide zit maar niet in beide.
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
print(a | b)
print(a & b)
print(a - b)
print(a ^ b)
Uitvoer:
{1, 2, 3, 4, 5, 6}
{3, 4}
{1, 2}
{1, 2, 5, 6}
De operatorvormen zijn alleen-lezen; de methodevormen accepteren elke iterabele aan de rechterkant, niet alleen een andere set (a.union([5, 6])). Kies wat in de context het beste leest.
2.10.6. Wat er in een set kan¶
Set-elementen moeten hashbaar zijn – dezelfde beperking als bij dict-sleutels. int, float, str, bool, bytes en tuple (wanneer de inhoud zelf hashbaar is) werken allemaal. list en dict niet; er een proberen toe te voegen veroorzaakt een TypeError.
2.10.7. frozenset¶
Een gewone set is veranderlijk: elke aanroep van add / remove / discard wijzigt het object ter plekke. Die veranderlijkheid maakt dat het niet hashbaar kan zijn, dus een set kan niet worden gebruikt als dict-sleutel of als lid van een andere set.
frozenset is de onveranderlijke tegenhanger. Het heeft dezelfde opzoekingen en operatoren (in, |, &, -, ^) als set, maar geen add / remove en geen methoden die muteren. Omdat de inhoud nooit kan veranderen, is de hash van een frozenset welgedefinieerd – dus het is hashbaar:
primary = frozenset({"red", "green", "blue"})
secondary = frozenset({"yellow", "purple", "orange"})
palettes = {
primary: "RGB",
secondary: "mixed",
}
print(palettes[primary])
Uitvoer:
RGB
Construeer een frozenset uit elke iterabele – frozenset() voor het lege geval, frozenset(some_set) om een onveranderlijke momentopname van een bestaande set te nemen:
snapshot = frozenset(s) # immutable copy of s
s.add("new") # snapshot does not change
Twee veelvoorkomende redenen om ernaar te grijpen:
Gebruik als dict-sleutel of set-lid. Overal waar een enkele waarde niet kan vatten wat je nodig hebt, kan een
frozensetvan waarden dat wel – “de set kenmerken die deze driver ondersteunt”, “de set pinnen die dit profiel gebruikt”.Een constante vastleggen. Een
frozensetop moduleniveau van toegestane namen kan niet per ongeluk door een aanroeper worden gemuteerd; een gewonesetwel. Geef de voorkeur aanfrozensetvoor alles wat na constructie alleen-lezen hoort te zijn.