2.10. Mulțimi#

O mulțime este o colecție neordonată de elemente unice. Adăugarea unei valori care este deja prezentă nu are niciun efect; iterarea produce fiecare valoare exact o dată. Mulțimile sunt instrumentul potrivit atunci când apartenența și eliminarea duplicatelor contează, iar ordonarea nu.

2.10.1. Crearea unei mulțimi#

Folosește acolade pentru o mulțime nevidă sau set() pentru una vidă:

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

Acoladele arată ca un literal dict; {} de unul singur este un dict vid, nu o mulțime vidă – unul dintre accidentele istorice ale Python. Folosește set() pentru cazul vid.

set() construiește de asemenea o mulțime dintr-un iterabil oarecare, ceea ce reprezintă modul standard de a elimina duplicatele dintr-o secvență:

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

Rezultat:

{1, 2, 3, 4}

Ordinea de afișare poate varia – mulțimile nu promit să itereze într-o anumită ordine.

2.10.2. Mulțime vs dict#

Atât mulțimile, cât și dicționarele stochează elemente unice într-un tabel de dispersie. Diferența o face ceea ce poartă cu sine fiecare element:

  • Un dict stochează perechi cheie-valoare. Căutarea unei chei returnează valoarea acesteia.

  • O set stochează doar elementele. Căutarea unui element îți spune dacă acesta există acolo.

Alegerea între cele două se reduce la dacă valoarea de lângă fiecare element înseamnă ceva:

  • Apelează la o mulțime când nicio valoare nu trebuie să stea lângă fiecare element – te interesează doar dacă elementul este prezent sau combini grupuri de elemente unice prin reuniune / intersecție.

  • Apelează la un dict când fiecare element este împerecheat cu date pe care căutarea trebuie să le recupereze – o hartă de configurare, un cache, un contor indexat după nume.

Cele două tipuri împărtășesc multă sintaxă de suprafață, de unde provine cea mai mare parte a confuziei. Diferențele într-un singur bloc:

set

dict

conține

elemente unice

chei unice, fiecare cu o valoare

literal populat

{1, 2, 3}

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

literal vid

set()

{}

test de apartenență

x in s

k in d (doar chei)

obține o valoare

n/a

d[k]

adaugă un element

s.add(x)

d[k] = v

iterare

produce elemente

produce chei (folosește d.items() pentru perechi)

Asimetria dintre literalii populați și cei vizi este capcana care merită semnalată:

  • Acoladele cu elemente în interior{1, 2, 3} – sunt un literal de mulțime; acoladele cu perechi cheie-valoare{"a": 1} – sunt un literal de dict. Parserul le diferențiază după ceea ce se află în interior.

  • Acoladele cu nimic în interior{} – sunt un dict vid, nu o mulțime vidă. Dicționarele au apărut primele; literalul vid le aparține. O mulțime vidă nu are deloc un literal cu acolade și trebuie scrisă set().

Un șablon comun atunci când doar cheile unui dict sunt vreodată citite este trecerea la o mulțime – aceasta face intenția evidentă și elimină valorile neutilizate din memorie.

2.10.3. Adăugare și eliminare#

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

Rezultat:

{1, 3, 4}

2.10.4. Apartenență#

Operatorul in testează apartenența. Pe o mulțime, acesta are un timp aproximativ constant, indiferent de dimensiune – ceea ce este motivul principal pentru a alege o mulțime în locul unei list atunci când trebuie doar să întrebi „se află această valoare acolo”:

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

O list cu același conținut ar scana de la început de fiecare dată, ceea ce este în regulă pentru zece elemente, dar lent pentru zece mii.

2.10.5. Operații pe mulțimi#

Două mulțimi pot fi combinate cu operațiile matematice obișnuite. Fiecare are atât o formă de operator, cât și o formă de metodă:

  • a | b sau a.union(b) – tot ce se află în oricare dintre mulțimi.

  • a & b sau a.intersection(b) – doar ceea ce apare în ambele.

  • a - b sau a.difference(b) – în a dar nu în b.

  • a ^ b sau a.symmetric_difference(b) – într-una, dar nu în ambele.

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

Rezultat:

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

Formele de operator sunt doar pentru citire; formele de metodă acceptă orice iterabil în dreapta, nu doar o altă mulțime (a.union([5, 6])). Alege oricare se citește mai bine în context.

2.10.6. Ce poate intra într-o mulțime#

Elementele unei mulțimi trebuie să fie hashable – aceeași constrângere ca pentru cheile dict. int, float, str, bool, bytes și tuple (când conținutul său este la rândul lui hashable) funcționează toate. list și dict nu; încercarea de a adăuga una declanșează TypeError.

2.10.7. frozenset#

O set obișnuită este mutabilă: fiecare apel la add / remove / discard schimbă obiectul pe loc. Această mutabilitate o descalifică de la a fi hashable, așa că o mulțime nu poate fi folosită ca o cheie dict sau ca membru al altei mulțimi.

frozenset este omologul imuabil. Are aceleași căutări și operatori (in, |, &, -, ^) ca set, dar fără add / remove și fără metode care modifică. Deoarece nimic nu îi poate schimba vreodată conținutul, hash-ul unui frozenset este bine definit – așa că este hashable:

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

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

print(palettes[primary])

Rezultat:

RGB

Construiește un frozenset dintr-un iterabil oarecare – frozenset() pentru cazul vid, frozenset(some_set) pentru a face un instantaneu imuabil al unei mulțimi existente:

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

Două motive comune pentru a apela la el:

  • Utilizare ca cheie de dict sau membru de mulțime. Oriunde o singură valoare nu poate captura ceea ce ai nevoie, un frozenset de valori poate – „mulțimea de caracteristici suportate de acest driver”, „mulțimea de pini pe care îi folosește acest profil”.

  • Blocarea unei constante. Un frozenset la nivel de modul de nume permise nu poate fi modificat accidental de un apelant; o set obișnuită poate. Preferă frozenset pentru orice este menit să fie doar pentru citire după construcție.