2.10. Sets#

En mängd (set) är en oordnad samling av unika element. Att lägga till ett värde som redan finns har ingen effekt; iterering ger varje värde exakt en gång. Mängder är rätt verktyg när medlemskap och borttagning av dubbletter är viktigt, men ordning inte är det.

2.10.1. Skapa en mängd#

Använd klammerparenteser för en icke-tom mängd, eller set() för en tom:

colours = {"red", "green", "blue"}
empty = set()

Klammerparenteserna ser ut som en dict-literal; {} för sig självt är en tom dict, inte en tom mängd – en av Pythons historiska olyckor. Använd set() för det tomma fallet.

set() bygger också en mängd från valfri itererbar, vilket är det vanliga sättet att ta bort dubbletter från en följd:

nums = [1, 2, 2, 3, 1, 4]
unique = set(nums)
print(unique)

Utdata:

{1, 2, 3, 4}

Utskriftsordningen kan variera – mängder lovar inte att iterera i någon särskild ordning.

2.10.2. Mängd kontra dict#

Både mängder och dictionaries lagrar unika element i en hashtabell. Skillnaden är vad varje element bär med sig:

  • En dict lagrar nyckel-värde-par. Att slå upp en nyckel returnerar dess värde.

  • En set lagrar enbart elementen. Att slå upp ett element talar om huruvida det finns där.

Valet mellan de två handlar om huruvida värdet bredvid varje element betyder något:

  • Välj en mängd när inget värde hör hemma bredvid varje element – du bryr dig bara om huruvida elementet finns, eller du kombinerar grupper av unika element med union/snitt.

  • Välj en dict när varje element är parat med data som uppslagningen är tänkt att hämta – en konfigurationskarta, en cache, en räknare indexerad med namn.

De två typerna delar mycket av ytsyntaxen, vilket är där det mesta av förvirringen kommer ifrån. Skillnaderna i ett block:

set

dict

rymmer

unika element

unika nycklar, var och en med ett värde

fylld literal

{1, 2, 3}

{"a": 1, "b": 2}

tom literal

set()

{}

medlemskapstest

x in s

k in d (endast nycklar)

hämta ett värde

ej tillämpligt

d[k]

lägga till ett element

s.add(x)

d[k] = v

iterera

ger element

ger nycklar (använd d.items() för par)

Asymmetrin mellan de fyllda och tomma literalerna är fallgropen värd att lyfta fram:

  • Klammerparenteser med element i sig{1, 2, 3} – är en mängdliteral; klammerparenteser med nyckel-värde-par{"a": 1} – är en dict-literal. Parsern skiljer dem åt utifrån vad som finns inuti.

  • Klammerparenteser med ingenting inuti{} – är en tom dict, inte en tom mängd. Dictionaries kom först; den tomma literalen tillhör dem. En tom mängd har ingen klammerliteral alls och måste skrivas set().

Ett vanligt mönster när endast nycklarna i en dict någonsin läses är att byta till en mängd – det gör avsikten uppenbar och rensar bort de oanvända värdena ur minnet.

2.10.3. Lägga till och ta bort#

s = {1, 2, 3}
s.add(4)
s.discard(99)            # silent: 99 not in s
s.remove(2)
print(s)

Utdata:

{1, 3, 4}

2.10.4. Medlemskap#

Operatorn in testar medlemskap. På en mängd är det ungefär konstant tid oavsett storlek – vilket är huvudskälet att välja en mängd framför en list när du bara behöver fråga ”finns detta värde där”:

if "red" in colours:
    print("colour is allowed")

En list med samma innehåll skulle söka igenom från början varje gång, vilket är okej för tio element men långsamt för tiotusen.

2.10.5. Mängdoperationer#

Två mängder kan kombineras med de vanliga matematiska operationerna. Var och en har både en operatorform och en metodform:

  • a | b eller a.union(b) – allt i någon av mängderna.

  • a & b eller a.intersection(b) – endast det som förekommer i båda.

  • a - b eller a.difference(b) – i a men inte i b.

  • a ^ b eller a.symmetric_difference(b) – i den ena men inte i båda.

a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
print(a | b)
print(a & b)
print(a - b)
print(a ^ b)

Utdata:

{1, 2, 3, 4, 5, 6}
{3, 4}
{1, 2}
{1, 2, 5, 6}

Operatorformerna är skrivskyddade; metodformerna accepterar valfri itererbar på höger sida, inte bara en annan mängd (a.union([5, 6])). Välj den som läser bäst i sammanhanget.

2.10.6. Vad som kan ligga i en mängd#

Element i en mängd måste vara hashbara – samma begränsning som för nycklar i dict. int, float, str, bool, bytes och tuple (när dess innehåll själv är hashbart) fungerar alla. list och dict gör det inte; att försöka lägga till en sådan utlöser TypeError.

2.10.7. frozenset#

En vanlig set är muterbar: varje anrop till add / remove / discard ändrar objektet på plats. Den muterbarheten diskvalificerar den från att vara hashbar, så en mängd kan inte användas som en nyckel i dict eller som medlem i en annan mängd.

frozenset är den oföränderliga motsvarigheten. Den har samma uppslagningar och operatorer (in, |, &, -, ^) som set, men inget add / remove och inga metoder som muterar. Eftersom ingenting någonsin kan ändra dess innehåll är hashen för en frozenset väldefinierad – så den är hashbar:

primary = frozenset({"red", "green", "blue"})
secondary = frozenset({"yellow", "purple", "orange"})

palettes = {
    primary: "RGB",
    secondary: "mixed",
}

print(palettes[primary])

Utdata:

RGB

Konstruera en frozenset från valfri itererbar – frozenset() för det tomma fallet, frozenset(some_set) för att ta en oföränderlig stillbild av en befintlig mängd:

snapshot = frozenset(s)         # immutable copy of s
s.add("new")                    # snapshot does not change

Två vanliga skäl att ta till den:

  • Använd som nyckel i en dict eller medlem i en mängd. Överallt där ett enskilt värde inte kan fånga det du behöver kan en frozenset av värden göra det – ”mängden av särdrag som denna drivrutin stöder”, ”mängden av stift som denna profil använder”.

  • Lås fast en konstant. En frozenset på modulnivå av tillåtna namn kan inte muteras av misstag av en anropare; en vanlig set kan det. Föredra frozenset för allt som är tänkt att vara skrivskyddat efter konstruktion.