16. Dizionari: mappare chiavi a valori
Nei capitoli precedenti, abbiamo imparato a conoscere le liste e le tuple—collezioni che memorizzano elementi in un ordine specifico e ci permettono di accedervi per posizione. Ma cosa succede se vogliamo cercare informazioni tramite qualcosa di più significativo di un numero? Cosa succede se vogliamo trovare il voto di uno studente in base al suo nome, o il prezzo di un prodotto in base al suo ID, o la definizione di una parola in base alla parola stessa?
È qui che entrano in gioco i dizionari. Un dizionario(dict) è la struttura dati integrata di Python per memorizzare coppie chiave-valore. Invece di accedere agli elementi tramite la loro posizione (come grades[0]), vi accediamo tramite la loro chiave (come grades["Alice"]). Questo rende i dizionari incredibilmente potenti per organizzare e recuperare dati in programmi reali.
Pensa a un dizionario come a un dizionario del mondo reale o a un elenco telefonico: cerchi una parola (la chiave) per trovarne la definizione (il valore), oppure cerchi un nome per trovare un numero di telefono. I dizionari Python funzionano allo stesso modo: mappano le chiavi sui valori, consentendo ricerche rapide e un’organizzazione flessibile dei dati.
16.1) Creare dizionari e accedere ai valori
16.1.1) Che cos’è un dizionario?
Un dizionario è una collezione di coppie chiave-valore. Ogni chiave è associata a un valore, e usi la chiave per recuperare il valore. Le chiavi devono essere uniche all’interno di un dizionario: non puoi avere due voci con la stessa chiave. I valori, invece, possono essere duplicati.
Ecco la struttura di base:
- Chiavi: identificatori univoci usati per cercare i valori (come nomi, ID o etichette)
- Valori: i dati associati a ciascuna chiave (come voti, prezzi o descrizioni)
I dizionari sono:
- Mutabili: puoi aggiungere, modificare o rimuovere coppie chiave-valore dopo la creazione
- Non ordinati (in Python 3.6 e versioni precedenti) oppure ordinati per inserimento (in Python 3.7+): anche se le versioni moderne di Python preservano l’ordine in cui aggiungi gli elementi, dovresti pensare ai dizionari come a collezioni in cui accedi agli elementi per chiave, non per posizione
- Dinamici: possono crescere e ridursi secondo necessità
16.1.2) Creare dizionari vuoti e semplici
Il modo più semplice per creare un dizionario è usare le parentesi graffe {} con coppie chiave-valore separate da due punti:
# Dizionario vuoto
empty_dict = {}
print(empty_dict) # Output: {}
print(type(empty_dict)) # Output: <class 'dict'>
# Dizionario con i voti degli studenti
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92}
# Dizionario con i prezzi dei prodotti
prices = {"apple": 0.50, "banana": 0.30, "orange": 0.75}
print(prices) # Output: {'apple': 0.5, 'banana': 0.3, 'orange': 0.75}Nota la sintassi: ogni coppia chiave-valore è scritta come key: value, e le coppie sono separate da virgole. Le chiavi qui sono stringhe ("Alice", "apple"), e i valori sono numeri, ma sia le chiavi sia i valori possono essere di molti tipi diversi.
Puoi anche creare un dizionario usando il costruttore dict():
# Usare dict() con argomenti keyword
student = dict(name="Alice", age=20, major="Computer Science")
print(student) # Output: {'name': 'Alice', 'age': 20, 'major': 'Computer Science'}
# Usare dict() con una lista di tuple
colors = dict([("red", "#FF0000"), ("green", "#00FF00"), ("blue", "#0000FF")])
print(colors) # Output: {'red': '#FF0000', 'green': '#00FF00', 'blue': '#0000FF'}Il costruttore dict() è utile quando stai costruendo dizionari a partire da altre strutture dati o quando vuoi usare identificatori Python come chiavi (senza virgolette).
16.1.3) Accedere ai valori tramite chiave
Per recuperare un valore da un dizionario, usa le parentesi quadre con la chiave:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Accedere a singoli valori
alice_grade = grades["Alice"]
print(alice_grade) # Output: 95
bob_grade = grades["Bob"]
print(bob_grade) # Output: 87Questo è il modo più diretto per accedere ai valori di un dizionario. Tuttavia, se provi ad accedere a una chiave che non esiste, Python solleva un KeyError:
grades = {"Alice": 95, "Bob": 87}
# WARNING: KeyError - for demonstration only
# print(grades["David"]) # PROBLEM: KeyError: 'David'Questo errore si verifica perché "David" non è una chiave nel dizionario. Impareremo come gestirlo in modo sicuro nella sottosezione successiva.
16.1.4) Accesso sicuro con get()
Per evitare KeyError quando una chiave potrebbe non esistere, usa il metodo get(). Restituisce None (o un valore predefinito che specifichi) se la chiave non viene trovata:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Accesso sicuro con get()
alice_grade = grades.get("Alice")
print(alice_grade) # Output: 95
# La chiave non esiste - restituisce None
david_grade = grades.get("David")
print(david_grade) # Output: None
# Fornire un valore predefinito
david_grade = grades.get("David", 0)
print(david_grade) # Output: 0
# Usare get() nella logica condizionale
if grades.get("Eve") is None:
print("Eve is not in the grade book") # Output: Eve is not in the grade bookIl metodo get() è più sicuro dell’accesso diretto con parentesi quadre quando non sei certo che una chiave esista. Il secondo argomento di get() è il valore predefinito da restituire se la chiave manca—se non ne fornisci uno, il valore predefinito è None.
Ecco un esempio pratico che mostra quando get() è utile:
# Database studenti con informazioni opzionali
students = {
"Alice": {"age": 20, "major": "CS"},
"Bob": {"age": 19}, # Bob non ha ancora dichiarato un major
"Charlie": {"major": "Math"} # Età di Charlie non registrata
}
# Accedere in modo sicuro a informazioni potenzialmente mancanti
for name in ["Alice", "Bob", "Charlie"]:
student = students[name]
age = student.get("age", "Unknown")
major = student.get("major", "Undeclared")
print(f"{name}: Age {age}, Major {major}")
# Output:
# Alice: Age 20, Major CS
# Bob: Age 19, Major Undeclared
# Charlie: Age Unknown, Major Math16.1.5) Tipi di chiave validi
Le chiavi dei dizionari devono essere hashable—un termine tecnico che significa che il valore della chiave non può cambiare. In pratica, questo significa:
Tipi di chiave validi (immutabili):
- Stringhe:
"name","id_123" - Numeri:
42,3.14 - Tuple (se contengono solo elementi immutabili):
(1, 2),("x", "y") - Booleani:
True,False None
Tipi di chiave non validi (mutabili):
- Liste:
[1, 2, 3]non può essere una chiave - Dizionari:
{"a": 1}non può essere una chiave - Insiemi:
{1, 2, 3}non può essere una chiave
# Chiavi valide
valid_dict = {
"name": "Alice", # Chiave stringa
42: "answer", # Chiave intero
3.14: "pi", # Chiave float
(1, 2): "coordinates", # Chiave tupla
True: "yes", # Chiave booleana
None: "nothing" # Chiave None
}
print(valid_dict["name"]) # Output: Alice
print(valid_dict[42]) # Output: answer
print(valid_dict[(1, 2)]) # Output: coordinates
# WARNING: Invalid keys - for demonstration only
# invalid_dict = {[1, 2]: "list key"} # PROBLEM: TypeError: unhashable type: 'list'
# invalid_dict = {{1, 2}: "set key"} # PROBLEM: TypeError: unhashable type: 'set'Errore comune dei principianti: Un errore frequente è provare a usare una lista come chiave di un dizionario perché sembra logico. Per esempio, potresti voler usare coordinate [x, y] come chiave per memorizzare dati di posizione. Quando Python solleva TypeError: unhashable type: 'list', i principianti spesso non capiscono perché—dopotutto, la lista contiene esattamente i dati che vogliono usare come chiave.
Il motivo è che le liste sono mutabili (possono cambiare), e Python ha bisogno che le chiavi dei dizionari siano stabili e non modificabili. Se hai bisogno di usare qualcosa di simile a una lista come chiave, convertila prima in una tupla: tuple([1, 2]) diventa (1, 2), che può essere usata come chiave. Le tuple sono immutabili, quindi funzionano perfettamente:
# Sbagliato: tentare di usare una lista come chiave
# locations = {[0, 0]: "origin", [1, 0]: "east"} # PROBLEM: TypeError
# Giusto: convertire in tupla
locations = {(0, 0): "origin", (1, 0): "east", (0, 1): "north"}
print(locations[(0, 0)]) # Output: origin
print(locations[(1, 0)]) # Output: eastI valori, invece, possono essere di qualsiasi tipo—mutabile o immutabile:
# I valori possono essere di qualsiasi tipo
flexible_dict = {
"numbers": [1, 2, 3], # Valore lista
"nested": {"a": 1, "b": 2}, # Valore dizionario
"function": len, # Valore funzione
"mixed": (1, [2, 3], {"x": 4}) # Tupla che contiene elementi mutabili
}
print(flexible_dict["numbers"]) # Output: [1, 2, 3]
print(flexible_dict["nested"]["a"]) # Output: 1Esploreremo la hashability in modo più approfondito nel Capitolo 17, ma per ora ricordati: usa tipi immutabili (stringhe, numeri, tuple) come chiavi, e andrà tutto bene.
16.2) Aggiungere e aggiornare voci del dizionario
16.2.1) Aggiungere nuove coppie chiave-valore
Aggiungere una nuova voce a un dizionario è semplice—basta assegnare un valore a una nuova chiave:
grades = {"Alice": 95, "Bob": 87}
print(grades) # Output: {'Alice': 95, 'Bob': 87}
# Aggiungere un nuovo studente
grades["Charlie"] = 92
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92}
# Aggiungere un altro studente
grades["Diana"] = 88
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92, 'Diana': 88}Se la chiave non esiste, Python la crea. Se esiste, Python aggiorna il valore (che vedremo nella prossima sezione).
16.2.2) Aggiornare valori esistenti
Per aggiornare un valore, assegna semplicemente un nuovo valore a una chiave esistente:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92}
# Aggiornare il voto di Bob
grades["Bob"] = 90
print(grades) # Output: {'Alice': 95, 'Bob': 90, 'Charlie': 92}
# Aggiornare più voti
grades["Alice"] = 98
grades["Charlie"] = 94
print(grades) # Output: {'Alice': 98, 'Bob': 90, 'Charlie': 94}Python non distingue tra aggiunta e aggiornamento—la sintassi è identica. Se la chiave esiste, il valore viene aggiornato; se no, viene creata una nuova voce.
Ecco un esempio pratico che mostra sia l’aggiunta sia l’aggiornamento:
# Tenere traccia dell'inventario
inventory = {"apple": 50, "banana": 30}
print("Initial inventory:", inventory) # Output: Initial inventory: {'apple': 50, 'banana': 30}
# Rifornire le mele (aggiornare esistente)
inventory["apple"] = inventory["apple"] + 20
print("After restocking apples:", inventory) # Output: After restocking apples: {'apple': 70, 'banana': 30}
# Aggiungere un nuovo prodotto (aggiungere nuova chiave)
inventory["orange"] = 40
print("After adding oranges:", inventory) # Output: After adding oranges: {'apple': 70, 'banana': 30, 'orange': 40}
# Vendere alcune banane (aggiornare esistente)
inventory["banana"] = inventory["banana"] - 10
print("After selling bananas:", inventory) # Output: After selling bananas: {'apple': 70, 'banana': 20, 'orange': 40}16.2.3) Usare update() per unire dizionari
Il metodo update() aggiunge più coppie chiave-valore in una volta oppure unisce un altro dizionario in quello corrente:
grades = {"Alice": 95, "Bob": 87}
print(grades) # Output: {'Alice': 95, 'Bob': 87}
# Aggiungere più studenti in una sola volta
new_students = {"Charlie": 92, "Diana": 88}
grades.update(new_students)
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92, 'Diana': 88}
# Aggiornare esistenti e aggiungere nuovi
more_updates = {"Bob": 90, "Eve": 85} # Il voto di Bob cambia, Eve è nuova
grades.update(more_updates)
print(grades) # Output: {'Alice': 95, 'Bob': 90, 'Charlie': 92, 'Diana': 88, 'Eve': 85}Il metodo update() modifica il dizionario sul posto. Se una chiave esiste già, il suo valore viene aggiornato; se no, la coppia chiave-valore viene aggiunta.
Puoi anche passare argomenti keyword a update():
student = {"name": "Alice", "age": 20}
print(student) # Output: {'name': 'Alice', 'age': 20}
# Aggiornare usando argomenti keyword
student.update(age=21, major="Computer Science")
print(student) # Output: {'name': 'Alice', 'age': 21, 'major': 'Computer Science'}Ecco un esempio pratico di unione di impostazioni di configurazione:
# Impostazioni predefinite
config = {
"theme": "light",
"font_size": 12,
"auto_save": True
}
# Preferenze utente (sovrascrivono alcune predefinite)
user_prefs = {
"theme": "dark",
"font_size": 14
}
# Unire le preferenze utente in config
config.update(user_prefs)
print(config) # Output: {'theme': 'dark', 'font_size': 14, 'auto_save': True}16.2.4) Usare setdefault() per aggiungere solo se la chiave non esiste
Il metodo setdefault() è utile quando vuoi aggiungere una coppia chiave-valore solo se la chiave non esiste già. Se la chiave esiste, restituisce il valore corrente senza modificarlo:
grades = {"Alice": 95, "Bob": 87}
# Aggiungere Charlie (la chiave non esiste)
result = grades.setdefault("Charlie", 90)
print(result) # Output: 90
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 90}
# Provare ad aggiungere Alice (la chiave esiste - nessuna modifica)
result = grades.setdefault("Alice", 80)
print(result) # Output: 95 (existing value returned)
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 90} (unchanged)Questo è particolarmente utile quando vuoi assicurarti che tutte le chiavi di configurazione richieste esistano con valori predefiniti, preservando al contempo eventuali valori che l’utente ha già personalizzato:
# Configurazione dell'applicazione con valori predefiniti
config = {"theme": "light", "font_size": 12}
# Assicurarsi che tutte le impostazioni richieste esistano con valori predefiniti
config.setdefault("auto_save", True)
config.setdefault("language", "en")
config.setdefault("theme", "dark") # Non cambierà - esiste già
print(config)
# Output: {'theme': 'light', 'font_size': 12, 'auto_save': True, 'language': 'en'}
# Ora accedere in modo sicuro a tutte le impostazioni
print(f"Theme: {config['theme']}") # Output: Theme: light
print(f"Auto-save: {config['auto_save']}") # Output: Auto-save: True
print(f"Language: {config['language']}") # Output: Language: en16.3) Rimuovere voci dal dizionario con del e pop()
16.3.1) Rimuovere voci con del
L’istruzione del rimuove una coppia chiave-valore da un dizionario:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92, 'Diana': 88}
# Rimuovere Charlie
del grades["Charlie"]
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Diana': 88}
# Rimuovere un altro studente
del grades["Bob"]
print(grades) # Output: {'Alice': 95, 'Diana': 88}Se provi a eliminare una chiave che non esiste, Python solleva un KeyError:
grades = {"Alice": 95, "Bob": 87}
# WARNING: KeyError - for demonstration only
# del grades["Charlie"] # PROBLEM: KeyError: 'Charlie'Per eliminare in modo sicuro una chiave che potrebbe non esistere, controlla prima:
grades = {"Alice": 95, "Bob": 87}
# Eliminazione sicura
if "Charlie" in grades:
del grades["Charlie"]
else:
print("Charlie not found") # Output: Charlie not found16.3.2) Rimuovere e recuperare con pop()
Il metodo pop() rimuove una chiave e restituisce il suo valore. Questo è utile quando devi sia rimuovere una voce sia usare il suo valore:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92}
# Rimuovere e ottenere il voto di Bob
bob_grade = grades.pop("Bob")
print(bob_grade) # Output: 87
print(grades) # Output: {'Alice': 95, 'Charlie': 92}
# Rimuovere e usare il valore
charlie_grade = grades.pop("Charlie")
print(f"Charlie's final grade was {charlie_grade}") # Output: Charlie's final grade was 92
print(grades) # Output: {'Alice': 95}Come del, pop() solleva un KeyError se la chiave non esiste. Tuttavia, puoi fornire un valore predefinito da restituire al suo posto:
grades = {"Alice": 95, "Bob": 87}
# Pop con valore predefinito (la chiave non esiste)
diana_grade = grades.pop("Diana", 0)
print(diana_grade) # Output: 0
print(grades) # Output: {'Alice': 95, 'Bob': 87} (unchanged)
# Pop con valore predefinito (la chiave esiste)
alice_grade = grades.pop("Alice", 0)
print(alice_grade) # Output: 95
print(grades) # Output: {'Bob': 87}16.3.3) Rimuovere tutte le voci con clear()
Il metodo clear() rimuove tutte le coppie chiave-valore da un dizionario, lasciandolo vuoto:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92}
# Cancellare tutte le voci
grades.clear()
print(grades) # Output: {}
print(len(grades)) # Output: 0Questo è più esplicito che riassegnare a un dizionario vuoto (grades = {}), soprattutto quando altre variabili fanno riferimento allo stesso dizionario:
# Dimostrare la differenza
grades = {"Alice": 95, "Bob": 87}
backup = grades # backup fa riferimento allo stesso dizionario
# Usare clear() - influisce su entrambe le variabili
grades.clear()
print(grades) # Output: {}
print(backup) # Output: {} (same dictionary was cleared)
# Ripristinare per l'esempio successivo
grades = {"Alice": 95, "Bob": 87}
backup = grades
# Riassegnare - influisce solo su grades
grades = {}
print(grades) # Output: {}
print(backup) # Output: {'Alice': 95, 'Bob': 87} (different dictionary now)Esploreremo questo comportamento più in dettaglio nel Capitolo 18 quando parleremo di semantica dei riferimenti, ma per ora ricordati: clear() svuota il dizionario esistente, mentre la riassegnazione crea un nuovo dizionario vuoto.
16.4) Oggetti vista del dizionario: keys(), values() e items()
16.4.1) Comprendere le viste del dizionario
I dizionari forniscono tre metodi che restituiscono oggetti vista—oggetti speciali che forniscono una vista dinamica delle chiavi, dei valori o delle coppie chiave-valore del dizionario. Queste viste riflettono automaticamente le modifiche al dizionario:
keys(): restituisce una vista di tutte le chiavivalues(): restituisce una vista di tutti i valoriitems(): restituisce una vista di tutte le coppie chiave-valore (come tuple)
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Ottenere le viste
keys_view = grades.keys()
values_view = grades.values()
items_view = grades.items()
print(keys_view) # Output: dict_keys(['Alice', 'Bob', 'Charlie'])
print(values_view) # Output: dict_values([95, 87, 92])
print(items_view) # Output: dict_items([('Alice', 95), ('Bob', 87), ('Charlie', 92)])Queste non sono liste—sono oggetti vista. Ma puoi convertirli in liste se necessario:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Convertire le viste in liste
keys_list = list(grades.keys())
values_list = list(grades.values())
items_list = list(grades.items())
print(keys_list) # Output: ['Alice', 'Bob', 'Charlie']
print(values_list) # Output: [95, 87, 92]
print(items_list) # Output: [('Alice', 95), ('Bob', 87), ('Charlie', 92)]16.4.2) Le viste sono dinamiche
Gli oggetti vista riflettono automaticamente le modifiche al dizionario:
grades = {"Alice": 95, "Bob": 87}
# Creare una vista
keys_view = grades.keys()
print(keys_view) # Output: dict_keys(['Alice', 'Bob'])
# Modificare il dizionario
grades["Charlie"] = 92
print(keys_view) # Output: dict_keys(['Alice', 'Bob', 'Charlie'])
# Rimuovere una voce
del grades["Bob"]
print(keys_view) # Output: dict_keys(['Alice', 'Charlie'])Questo comportamento dinamico è utile quando devi lavorare con contenuti del dizionario che potrebbero cambiare. Tuttavia, se hai bisogno di un’istantanea che non cambi, converti la vista in una lista:
grades = {"Alice": 95, "Bob": 87}
# Creare un'istantanea
keys_snapshot = list(grades.keys())
print(keys_snapshot) # Output: ['Alice', 'Bob']
# Modificare il dizionario
grades["Charlie"] = 92
print(keys_snapshot) # Output: ['Alice', 'Bob'] (unchanged)16.4.3) Lavorare con keys()
Il metodo keys() restituisce una vista di tutte le chiavi del dizionario. Questo è utile per controllare quali chiavi esistono o per iterare su di esse:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Ottenere tutte le chiavi
keys = grades.keys()
print(keys) # Output: dict_keys(['Alice', 'Bob', 'Charlie'])
# Controllare se una chiave esiste
if "Alice" in keys:
print("Alice is in the grade book") # Output: Alice is in the grade book
# Contare le chiavi
print(f"Number of students: {len(keys)}") # Output: Number of students: 3Puoi anche controllare l’appartenenza direttamente sul dizionario senza chiamare keys():
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Questi sono equivalenti
if "Alice" in grades.keys():
print("Found (using keys())")
if "Alice" in grades:
print("Found (direct check)") # This is more common and concise
# Output:
# Found (using keys())
# Found (direct check)16.4.4) Lavorare con values()
Il metodo values() restituisce una vista di tutti i valori del dizionario. Questo è utile quando devi elaborare i valori senza importarti delle loro chiavi:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
# Ottenere tutti i valori
values = grades.values()
print(values) # Output: dict_values([95, 87, 92, 88])
# Calcolare statistiche
total = sum(values)
count = len(values)
average = total / count
print(f"Total points: {total}") # Output: Total points: 362
print(f"Number of students: {count}") # Output: Number of students: 4
print(f"Average grade: {average}") # Output: Average grade: 90.5
# Trovare voto massimo e minimo
print(f"Highest grade: {max(values)}") # Output: Highest grade: 95
print(f"Lowest grade: {min(values)}") # Output: Lowest grade: 8716.4.5) Lavorare con items()
Il metodo items() restituisce una vista di coppie chiave-valore come tuple. Questa è la vista più usata perché ti dà sia le chiavi sia i valori:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Ottenere tutte le coppie chiave-valore
items = grades.items()
print(items) # Output: dict_items([('Alice', 95), ('Bob', 87), ('Charlie', 92)])
# Convertire in lista per vedere chiaramente le tuple
items_list = list(items)
print(items_list) # Output: [('Alice', 95), ('Bob', 87), ('Charlie', 92)]
# Accedere a singole tuple
first_item = items_list[0]
print(first_item) # Output: ('Alice', 95)
print(first_item[0]) # Output: Alice
print(first_item[1]) # Output: 95La vista items() è particolarmente utile per l’iterazione, che tratteremo in dettaglio nella prossima sezione. Ecco un’anteprima:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Elaborare ogni coppia chiave-valore
for name, grade in grades.items():
print(f"{name}: {grade}")
# Output:
# Alice: 95
# Bob: 87
# Charlie: 9216.5) Iterare su chiavi, valori e elementi
16.5.1) Iterare sulle chiavi (comportamento predefinito)
Quando iteri su un dizionario direttamente con un ciclo(loop) for, iteri sulle sue chiavi:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Iterare sulle chiavi (implicito)
for name in grades:
print(name)
# Output:
# Alice
# Bob
# CharlieQuesto è equivalente a iterare su grades.keys():
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Iterare sulle chiavi (esplicito)
for name in grades.keys():
print(name)
# Output:
# Alice
# Bob
# CharlieEntrambi gli approcci funzionano in modo identico. La versione implicita (senza .keys()) è più comune e concisa.
Ecco un esempio pratico che usa le chiavi per accedere ai valori:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
# Trovare studenti che hanno superato (grade >= 90)
passing_students = []
for name in grades:
if grades[name] >= 90:
passing_students.append(name)
print("Students who passed:", passing_students) # Output: Students who passed: ['Alice', 'Charlie']16.5.2) Iterare sui valori
Per iterare solo sui valori, usa values():
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
# Iterare sui valori
for grade in grades.values():
print(grade)
# Output:
# 95
# 87
# 92
# 88Questo è utile quando devi elaborare i valori ma non ti interessa a quale chiave siano associati:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
# Calcolare totale e media
total = 0
count = 0
for grade in grades.values():
total = total + grade
count = count + 1
average = total / count
print(f"Class average: {average}") # Output: Class average: 90.5
# Controllare se tutti gli studenti hanno superato
all_passed = True
for grade in grades.values():
if grade < 60:
all_passed = False
break
if all_passed:
print("All students passed!") # Output: All students passed!16.5.3) Iterare su coppie chiave-valore con items()
Lo schema di iterazione più comune e utile è usare items() per ottenere sia chiavi sia valori:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Iterare sulle coppie chiave-valore
for name, grade in grades.items():
print(f"{name} scored {grade}")
# Output:
# Alice scored 95
# Bob scored 87
# Charlie scored 92Nota l’unpacking della tupla: for name, grade in grades.items(). Ogni elemento è una tupla come ("Alice", 95), e la spacchettiamo in due variabili. Questo è molto più leggibile rispetto ad accedere agli indici della tupla:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Senza unpacking (meno leggibile)
for item in grades.items():
print(f"{item[0]} scored {item[1]}")
# Con unpacking (più leggibile)
for name, grade in grades.items():
print(f"{name} scored {grade}")
# Both produce the same output:
# Alice scored 95
# Bob scored 87
# Charlie scored 9216.5.4) Modificare i dizionari durante l’iterazione
Avvertenza: Modificare la dimensione di un dizionario (aggiungendo o rimuovendo chiavi) mentre lo si itera può causare errori o comportamenti inattesi:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# WARNING: RuntimeError - for demonstration only
# for name in grades:
# if grades[name] < 90:
# del grades[name] # PROBLEM: RuntimeError: dictionary changed size during iterationNelle versioni moderne di Python (3.7+), questo solleva immediatamente un RuntimeError non appena provi a cambiare la dimensione del dizionario. Python rileva la modifica e interrompe l’esecuzione per prevenire comportamenti imprevedibili.
Nelle versioni più vecchie di Python, questo poteva far sì che l’iteratore:
- Saltasse elementi che avrebbe dovuto elaborare
- Elaborasse lo stesso elemento due volte
- Producesse risultati incoerenti
È per questo che ora Python fallisce subito con un messaggio di errore chiaro.
Se devi modificare un dizionario durante l’iterazione, itera su una copia delle chiavi:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
# Sicuro: iterare su una copia delle chiavi
for name in list(grades.keys()):
if grades[name] < 90:
del grades[name]
print(grades) # Output: {'Alice': 95, 'Charlie': 92}Oppure costruisci un nuovo dizionario con solo le voci che ti servono:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
# Costruire un nuovo dizionario con voci filtrate
high_grades = {}
for name, grade in grades.items():
if grade >= 90:
high_grades[name] = grade
print(high_grades) # Output: {'Alice': 95, 'Charlie': 92}Il secondo approccio è spesso più chiaro e sicuro. Impareremo modi ancora più eleganti per farlo usando le dictionary comprehensions nel Capitolo 35.
16.6) Metodi comuni dei dizionari
16.6.1) Controllare le chiavi con in e not in
Gli operatori in e not in controllano se una chiave esiste in un dizionario:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Controllare se le chiavi esistono
if "Alice" in grades:
print("Alice is in the grade book") # Output: Alice is in the grade book
if "David" not in grades:
print("David is not in the grade book") # Output: David is not in the grade bookQuesto è il modo preferito per controllare l’esistenza di una chiave prima di accedere ai valori. È più leggibile e “Pythonic” che usare get() e controllare None:
grades = {"Alice": 95, "Bob": 87}
# Preferito: usare in
if "Alice" in grades:
print(f"Alice's grade: {grades['Alice']}") # Output: Alice's grade: 95
# Alternativa: usare get() e controllare None
if grades.get("Alice") is not None:
print(f"Alice's grade: {grades['Alice']}") # Output: Alice's grade: 9516.6.2) Ottenere il numero di voci con len()
La funzione len() restituisce il numero di coppie chiave-valore in un dizionario:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
print(len(grades)) # Output: 3
# Dizionario vuoto
empty = {}
print(len(empty)) # Output: 0
# Dopo modifiche
grades["Diana"] = 88
print(len(grades)) # Output: 4
del grades["Bob"]
print(len(grades)) # Output: 3Questo è utile per controllare se un dizionario è vuoto o per riportare statistiche:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
if len(grades) == 0:
print("No students in grade book")
else:
total = sum(grades.values())
average = total / len(grades)
print(f"{len(grades)} students, average grade: {average}")
# Output: 4 students, average grade: 90.516.6.3) Copiare dizionari con copy()
Il metodo copy() crea una copia superficiale (shallow copy) di un dizionario—un nuovo dizionario con le stesse coppie chiave-valore:
original = {"Alice": 95, "Bob": 87}
duplicate = original.copy()
print(original) # Output: {'Alice': 95, 'Bob': 87}
print(duplicate) # Output: {'Alice': 95, 'Bob': 87}
# Modificare la copia
duplicate["Charlie"] = 92
print(original) # Output: {'Alice': 95, 'Bob': 87} (unchanged)
print(duplicate) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92}Questo è diverso dalla semplice assegnazione, che crea un riferimento allo stesso dizionario:
original = {"Alice": 95, "Bob": 87}
reference = original # Non una copia - stesso dizionario
# Modificare tramite reference
reference["Charlie"] = 92
print(original) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92} (changed!)
print(reference) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92}Esploreremo in dettaglio copie superficiali vs profonde nel Capitolo 18. Per ora, ricordati: usa copy() quando vuoi un duplicato indipendente di un dizionario.
16.6.4) Unire dizionari con l’operatore | (Python 3.9+)
Python 3.9 ha introdotto l’operatore | per unire dizionari. L’operatore | crea un nuovo dizionario che combina tutte le chiavi di entrambi i dizionari. In caso di chiavi duplicate, il valore a destra sovrascrive il valore a sinistra. Entrambi i dizionari originali rimangono invariati.
defaults = {"theme": "light", "font_size": 12, "auto_save": True}
user_prefs = {"theme": "dark", "font_size": 14}
# Unire i dizionari (user_prefs sovrascrive defaults)
config = defaults | user_prefs
print(config) # Output: {'theme': 'dark', 'font_size': 14, 'auto_save': True}
# Dizionari originali invariati
print(defaults) # Output: {'theme': 'light', 'font_size': 12, 'auto_save': True}
print(user_prefs) # Output: {'theme': 'dark', 'font_size': 14}Ecco un altro esempio che mostra come questo sia utile per combinare dati da più fonti:
# Informazioni sui prodotti da due fornitori
supplier_a = {
"laptop": {"price": 999.99, "stock": 15},
"mouse": {"price": 29.99, "stock": 50}
}
supplier_b = {
"laptop": {"price": 949.99, "stock": 20}, # Prezzo e stock migliori
"keyboard": {"price": 79.99, "stock": 30}
}
# Unire: i dati di supplier_b sovrascrivono quelli di supplier_a per i prodotti corrispondenti
combined = supplier_a | supplier_b
print(combined)
# Output: {'laptop': {'price': 949.99, 'stock': 20}, 'mouse': {'price': 29.99, 'stock': 50}, 'keyboard': {'price': 79.99, 'stock': 30}}
# Ora abbiamo laptop da supplier_b (offerta migliore), mouse da supplier_a e keyboard da supplier_bL’operatore |= unisce sul posto (modifica il dizionario di sinistra):
config = {"theme": "light", "font_size": 12, "auto_save": True}
user_prefs = {"theme": "dark", "font_size": 14}
# Unire sul posto
config |= user_prefs
print(config) # Output: {'theme': 'dark', 'font_size': 14, 'auto_save': True}Questo è equivalente a usare update() ma più conciso:
config = {"theme": "light", "font_size": 12}
user_prefs = {"theme": "dark", "font_size": 14}
# Questi sono equivalenti
config.update(user_prefs)
# config |= user_prefs
print(config) # Output: {'theme': 'dark', 'font_size': 14}16.7) Dizionari annidati per dati strutturati
16.7.1) Creare dizionari annidati
Perché usare dizionari annidati? Immagina di tenere traccia delle informazioni degli studenti. Potresti creare dizionari separati: ages = {"Alice": 20, "Bob": 19}, majors = {"Alice": "CS", "Bob": "Math"}, gpas = {"Alice": 3.8, "Bob": 3.6}. Ma questo diventa ingombrante—devi mantenere sincronizzati tre dizionari, e cercare tutte le informazioni per uno studente richiede tre ricerche separate. I dizionari annidati risolvono questo problema raggruppando i dati correlati: un nome di studente mappa a tutte le sue informazioni in una sola ricerca.
Un dizionario annidato è un dizionario che contiene altri dizionari come valori. Questo è utile per rappresentare dati strutturati e gerarchici:
# Registri studenti con più attributi
students = {
"Alice": {
"age": 20,
"major": "Computer Science",
"gpa": 3.8
},
"Bob": {
"age": 19,
"major": "Mathematics",
"gpa": 3.6
},
"Charlie": {
"age": 21,
"major": "Physics",
"gpa": 3.9
}
}
print(students)
# Output: {'Alice': {'age': 20, 'major': 'Computer Science', 'gpa': 3.8}, 'Bob': {'age': 19, 'major': 'Mathematics', 'gpa': 3.6}, 'Charlie': {'age': 21, 'major': 'Physics', 'gpa': 3.9}}Ogni nome di studente mappa a un altro dizionario contenente i suoi attributi. Questa struttura è molto più flessibile e manutenibile rispetto all’uso di dizionari separati per ciascun attributo.
16.7.2) Accedere a valori annidati
Per accedere a valori annidati, usa più parentesi quadre:
students = {
"Alice": {"age": 20, "major": "Computer Science", "gpa": 3.8},
"Bob": {"age": 19, "major": "Mathematics", "gpa": 3.6}
}
# Accedere a valori annidati
alice_age = students["Alice"]["age"]
print(alice_age) # Output: 20
bob_major = students["Bob"]["major"]
print(bob_major) # Output: Mathematics
# Usare nelle espressioni
print(f"Alice is {students['Alice']['age']} years old")
# Output: Alice is 20 years old
print(f"Bob's GPA: {students['Bob']['gpa']}")
# Output: Bob's GPA: 3.6Ogni parentesi accede a un livello di annidamento. Prima students["Alice"] ottiene il dizionario interno, poi ["age"] ottiene il valore dell’età da quel dizionario.
16.7.3) Accedere in modo sicuro a valori annidati
Quando accedi a dizionari annidati, devi controllare che ogni livello esista:
students = {
"Alice": {"age": 20, "major": "Computer Science"},
"Bob": {"age": 19} # Bob non ha dichiarato un major
}
# Unsafe access - might raise KeyError
# print(students["Bob"]["major"]) # PROBLEM: KeyError: 'major'
# Accesso sicuro con controlli multipli
if "Bob" in students:
if "major" in students["Bob"]:
print(f"Bob's major: {students['Bob']['major']}")
else:
print("Bob hasn't declared a major") # Output: Bob hasn't declared a major
# Usare get() per un accesso annidato sicuro
bob_major = students.get("Bob", {}).get("major", "Undeclared")
print(f"Bob's major: {bob_major}") # Output: Bob's major: UndeclaredLa catena di get() funziona perché se "Bob" non esiste, get() restituisce un dizionario vuoto {}, e quindi il secondo get() restituisce in modo sicuro "Undeclared".
16.7.4) Modificare dizionari annidati
Puoi aggiungere, aggiornare o rimuovere voci in dizionari annidati:
students = {
"Alice": {"age": 20, "major": "Computer Science", "gpa": 3.8},
"Bob": {"age": 19, "major": "Mathematics", "gpa": 3.6}
}
# Aggiornare un valore annidato
students["Alice"]["gpa"] = 3.9
print(f"Alice's new GPA: {students['Alice']['gpa']}") # Output: Alice's new GPA: 3.9
# Aggiungere un nuovo attributo a uno studente esistente
students["Bob"]["email"] = "bob@university.edu"
print(students["Bob"])
# Output: {'age': 19, 'major': 'Mathematics', 'gpa': 3.6, 'email': 'bob@university.edu'}
# Aggiungere un nuovo studente con dati annidati
students["Charlie"] = {
"age": 21,
"major": "Physics",
"gpa": 3.7
}
print(f"Number of students: {len(students)}") # Output: Number of students: 3
# Rimuovere un attributo
del students["Bob"]["email"]
print(students["Bob"])
# Output: {'age': 19, 'major': 'Mathematics', 'gpa': 3.6}16.7.5) Iterare su dizionari annidati
Puoi iterare su dizionari annidati usando cicli(loop) annidati:
students = {
"Alice": {"age": 20, "major": "Computer Science", "gpa": 3.8},
"Bob": {"age": 19, "major": "Mathematics", "gpa": 3.6},
"Charlie": {"age": 21, "major": "Physics", "gpa": 3.9}
}
# Iterare sugli studenti e i loro attributi
for name, info in students.items():
print(f"\n{name}:")
for key, value in info.items():
print(f" {key}: {value}")
# Output:
# Alice:
# age: 20
# major: Computer Science
# gpa: 3.8
#
# Bob:
# age: 19
# major: Mathematics
# gpa: 3.6
#
# Charlie:
# age: 21
# major: Physics
# gpa: 3.9Ecco un esempio pratico che trova studenti che soddisfano determinati criteri:
students = {
"Alice": {"age": 20, "major": "Computer Science", "gpa": 3.8},
"Bob": {"age": 19, "major": "Mathematics", "gpa": 3.6},
"Charlie": {"age": 21, "major": "Physics", "gpa": 3.9},
"Diana": {"age": 20, "major": "Computer Science", "gpa": 3.5}
}
# Trovare studenti CS con GPA sopra 3.7
print("High-performing CS students:")
for name, info in students.items():
if info["major"] == "Computer Science" and info["gpa"] >= 3.7:
print(f" {name} (GPA: {info['gpa']})")
# Output:
# High-performing CS students:
# Alice (GPA: 3.8)I dizionari sono una delle strutture dati più potenti e versatili di Python. Offrono ricerche rapide, organizzazione flessibile e soluzioni eleganti a problemi comuni di programmazione. Man mano che continui a imparare Python, troverai dizionari ovunque—dalle impostazioni di configurazione all’elaborazione dei dati fino alla costruzione di applicazioni complesse.
Gli schemi che abbiamo coperto in questo capitolo—conteggio, raggruppamento, tabelle di lookup e trasformazione dei dati—costituiscono la base per lavorare con dati strutturati in Python. Nel prossimo capitolo, esploreremo gli insiemi(set), un altro tipo di collezione che completa i dizionari per lavorare con dati unici e non ordinati.