Dizionari in Python (dict): creazione, metodi, cicli, ordinamento e frozendict
Il dizionario in Python (dict) è una collezione modificabile di coppie «chiave — valore» che restituisce il valore per chiave quasi istantaneamente; si crea con le graffe {"caffè": 250} oppure con la funzione dict(). Le chiavi sono uniche e devono essere hashable, dalla versione 3.7 l'ordine di inserimento viene conservato, e in 3.15 è comparso il tipo immutabile integrato frozendict.
Il dizionario in Python (dict) è una collezione di coppie «chiave — valore» in cui il valore si trova per chiave quasi istantaneamente, qualunque sia il numero di elementi. Se dovete sommare le spese per categoria, conservare impostazioni o cercare un utente per id, usate un dizionario. Qui sotto trovate tutto ciò che serve per lavorare con il dict di Python: creazione, metodi, cicli, ordinamento, unione e la novità della 3.15, frozendict.
Che cos'è un dizionario in Python
Il dizionario memorizza coppie in cui la chiave è unica: se scrivete un valore con una chiave già esistente, quello vecchio viene sostituito. Internamente è una tabella hash, quindi cercare, aggiungere e rimuovere per chiave richiede in media un tempo costante, e non un ciclo su tutti gli elementi.
Come chiave si può usare solo un oggetto hashable: una stringa, un numero, una tupla di valori immutabili. Una lista non può essere una chiave e produce TypeError: unhashable type: 'list'. I valori, invece, possono essere qualsiasi cosa, compresi altri dizionari e liste.
Dalla versione 3.7 il dizionario conserva con certezza l'ordine di inserimento: scorrendolo si ottengono le chiavi nell'ordine in cui sono state aggiunte.
Come creare un dizionario
Esistono più modi per creare un dizionario, e tutti compaiono nel codice reale.
spent = {"Cibo": 1200, "Taxi": 450} # letterale
empty = {} # dizionario vuoto
also_empty = dict() # lo stesso tramite la funzione dict
user = dict(name="Anna", age=30) # argomenti con nome
pairs = dict([("a", 1), ("b", 2)]) # da una lista di coppie
keys = dict.fromkeys(["x", "y"], 0) # {'x': 0, 'y': 0}
Attenzione: le graffe vuote {} creano un dizionario vuoto, non un set. Un set vuoto si crea solo con set().
Un modo comodo per costruire un dizionario da due liste è zip:
names = ["Cibo", "Taxi", "Cinema"]
sums = [1200, 450, 600]
spent = dict(zip(names, sums))
print(spent) # {'Cibo': 1200, 'Taxi': 450, 'Cinema': 600}
Accesso per chiave: d[k] e get
Il valore si legge tra parentesi quadre. Se la chiave manca, Python solleva KeyError. Per evitare l'errore si usa il metodo get: restituisce None oppure un valore predefinito.
spent = {"Cibo": 1200}
print(spent["Cibo"]) # 1200
print(spent.get("Cinema")) # None
print(spent.get("Cinema", 0)) # 0
spent["Cinema"] # KeyError: 'Cinema'
Per verificare se una chiave esiste si usa l'operatore in: "Cibo" in spent restituisce True. Controlla le chiavi, non i valori. Per i valori scrivete 1200 in spent.values().
Aggiunta, modifica e rimozione
Aggiungere o aggiornare una voce richiede una sola riga di assegnazione. Per i conteggi è molto comoda la combinazione con get:
spent = {}
for category, amount in [("Cibo", 250), ("Taxi", 300), ("Cibo", 400)]:
spent[category] = spent.get(category, 0) + amount
print(spent) # {'Cibo': 650, 'Taxi': 300}
Una chiave si rimuove con del d[k] (genera KeyError se la chiave manca) oppure con d.pop(k, default), che restituisce anche il valore. Per svuotare completamente il dizionario si usa d.clear(). La lunghezza, cioè il numero di coppie, è len(d).
Metodi dei dizionari: riepilogo
| Metodo | Che cosa fa |
|---|---|
get(k, default) | valore per la chiave oppure default, senza errori |
setdefault(k, default) | restituisce il valore; se la chiave manca, scrive prima default |
update(other) | aggiunge e sovrascrive le coppie di un altro dizionario |
pop(k, default) | rimuove la chiave e restituisce il valore |
popitem() | rimuove e restituisce l'ultima coppia aggiunta |
keys(), values(), items() | viste su chiavi, valori e coppie |
copy() | copia superficiale |
clear() | svuota il dizionario |
fromkeys(keys, value) | nuovo dizionario con lo stesso valore per tutte le chiavi |
setdefault: riempire il dizionario con liste
Il metodo setdefault è utile quando i valori sono liste e bisogna raggruppare gli elementi:
groups = {}
for item, cat in [("caffè", "Cibo"), ("metro", "Trasporti"), ("pizza", "Cibo")]:
groups.setdefault(cat, []).append(item)
print(groups) # {'Cibo': ['caffè', 'pizza'], 'Trasporti': ['metro']}
update: aggiornare il dizionario
update modifica il dizionario sul posto: le chiavi nuove vengono aggiunte, quelle già presenti vengono sovrascritte.
cfg = {"debug": False, "lang": "it"}
cfg.update({"debug": True, "theme": "dark"})
print(cfg) # {'debug': True, 'lang': 'it', 'theme': 'dark'}
defaultdict: dizionario con valore predefinito
collections.defaultdict è una sottoclasse di dict che, quando si accede a una chiave mancante, crea da sola il valore tramite una funzione di costruzione. Il codice di conteggio e raggruppamento diventa più breve.
from collections import defaultdict
spent = defaultdict(int)
for cat, amount in [("Cibo", 250), ("Taxi", 300), ("Cibo", 400)]:
spent[cat] += amount
print(dict(spent)) # {'Cibo': 650, 'Taxi': 300}
groups = defaultdict(list)
groups["Cibo"].append("caffè")
Per il semplice conteggio di occorrenze esiste anche collections.Counter: Counter(["a", "b", "a"]) restituisce Counter({'a': 2, 'b': 1}).
Scorrere un dizionario: ciclo for
Il ciclo for su un dizionario scorre per impostazione predefinita le chiavi. Per ottenere subito chiave e valore si scorre items(), la variante più usata.
spent = {"Cibo": 650, "Taxi": 300}
for cat in spent: # chiavi
print(cat)
for amount in spent.values(): # valori
print(amount)
for cat, amount in spent.items(): # coppie
print(f"{cat}: {amount} €")
Non si può cambiare la dimensione del dizionario durante il ciclo: si ottiene RuntimeError: dictionary changed size during iteration. Se bisogna eliminare alcune chiavi, scorrete una copia: for k in list(d):.
Stampare un dizionario
print(d) stampa il dizionario su una sola riga. Per un dizionario grande o annidato è più comodo il modulo pprint oppure json.dumps(d, ensure_ascii=False, indent=2): mostra i dati con i rientri e con i caratteri accentati senza codifiche di escape.
Ordinare un dizionario
Il dizionario non si ordina sul posto, non ha un metodo sort. Ordinare un dizionario significa creare un nuovo dizionario dalle coppie ordinate con sorted.
spent = {"Taxi": 300, "Cibo": 650, "Cinema": 120}
by_key = dict(sorted(spent.items()))
print(by_key) # {'Cibo': 650, 'Cinema': 120, 'Taxi': 300}
by_value = dict(sorted(spent.items(), key=lambda p: p[1], reverse=True))
print(by_value) # {'Cibo': 650, 'Taxi': 300, 'Cinema': 120}
Se vi servono solo le prime N categorie, non riconvertite il risultato in dizionario: sorted(spent.items(), key=lambda p: p[1], reverse=True)[:2] restituisce una lista di coppie.
Una lista di dizionari si ordina per campo così: sorted(users, key=lambda u: u["age"]).
Unione e somma di dizionari
Dalla versione 3.9 i dizionari si uniscono con l'operatore |, mentre |= aggiorna il dizionario sul posto. In caso di chiavi coincidenti vince l'operando di destra.
a = {"x": 1, "y": 2}
b = {"y": 20, "z": 3}
print(a | b) # {'x': 1, 'y': 20, 'z': 3}
print({**a, **b}) # lo stesso tramite unpacking
a |= b # come a.update(b)
Attenzione: a + b con i dizionari non funziona e genera TypeError. Se invece volete sommare i valori delle chiavi coincidenti, usate Counter: Counter(a) + Counter(b).
Unpacking di un dizionario
Due asterischi ** scompattano un dizionario in argomenti con nome di una funzione oppure in un altro dizionario:
def greet(name, city):
return f"{name} da {city}"
data = {"name": "Anna", "city": "Milano"}
print(greet(**data)) # Anna da Milano
Dict comprehension
La dict comprehension costruisce un dizionario in una riga a partire da qualsiasi sequenza:
squares = {n: n * n for n in range(5)}
print(squares) # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
big = {k: v for k, v in spent.items() if v > 200}
inverted = {v: k for k, v in {"a": 1, "b": 2}.items()}
print(inverted) # {1: 'a', 2: 'b'}
L'ultimo esempio mostra come invertire un dizionario, scambiando chiavi e valori. Funziona se i valori sono unici e hashable; altrimenti alcune coppie andranno perse.
Dizionari annidati
Un valore può essere a sua volta un dizionario: così si descrivono strutture come le risposte JSON di un'API oppure un dizionario di dizionari di utenti.
users = {
101: {"name": "Anna", "skills": ["python", "sql"]},
102: {"name": "Luca", "skills": ["go"]},
}
print(users[101]["skills"][0]) # python
print(users.get(103, {}).get("name")) # None, senza KeyError
Copiare un dizionario: copia e secondo nome
L'istruzione b = a non copia il dizionario: applica un secondo nome allo stesso oggetto. Se modificate tramite b, le modifiche si vedono anche in a. Lo stesso accade quando un dizionario viene passato a una funzione: questa riceve un riferimento all'oggetto condiviso e può alterarlo senza che nessuno se ne accorga.
import copy
a = {"limits": [0, 500]}
b = a.copy() # copia superficiale
b["limits"].append(1000) # la lista annidata è condivisa!
print(a) # {'limits': [0, 500, 1000]}
c = copy.deepcopy(a) # copia completamente indipendente
Confronto di dizionari
L'operatore == confronta i dizionari per contenuto: sono uguali se coincidono tutte le coppie. L'ordine delle chiavi non conta: {"a": 1, "b": 2} == {"b": 2, "a": 1} restituisce True. Per trovare le chiavi in comune si usano le operazioni sugli insiemi sulle viste: a.keys() & b.keys().
Dizionario tipizzato: annotazioni
L'annotazione di un dizionario si scrive come dict[str, int]. Se il dizionario ha un insieme fisso di campi di tipi diversi, è comodo TypedDict del modulo typing:
from typing import TypedDict
class User(TypedDict):
name: str
age: int
u: User = {"name": "Anna", "age": 30}
Le annotazioni non vengono verificate durante l'esecuzione: le leggono gli IDE e gli analizzatori statici come mypy.
Dizionario immutabile: frozendict in Python 3.15
In Python 3.15 è comparso il tipo integrato frozendict (PEP 814): non serve alcun import, e ogni tentativo di modificarlo genera subito TypeError. Prima, un dizionario immutabile si doveva simulare con un wrapper del modulo types o con un pacchetto esterno. È esattamente ciò che serve per le impostazioni comuni, che nessuna funzione deve cambiare silenziosamente.
config = frozendict(min_amount=0, max_amount=500)
config["max_amount"] = 1000
# TypeError: 'frozendict' object does not support item assignment
strict = config | {"max_amount": 300} # nuovo frozendict, l'originale resta intatto
cache = {config: "report di maggio"} # è hashable, quindi può essere una chiave
Ecco che cosa conviene sapere, secondo il materiale del corso:
frozendicteredita daobject, non dadict, quindiisinstance(cfg, dict)restituisceFalse. Per verificare «qualsiasi dizionario» usatecollections.abc.Mappingoppureisinstance(x, (dict, frozendict)).- È hashable solo se lo sono chiavi e valori:
frozendict(a=[1])si crea, mahash()dà errore. L'immutabilità è superficiale, come quella della tupla. - L'ordine viene conservato, ma
==non lo considera. json.dumps,pickleecopygestiscono il frozendict.
Errori tipici
- KeyError in lettura — usate
getoppure un controllo conin. - Una lista come chiave — trasformatela in tupla:
d[tuple(lst)]. - Modifica del dizionario durante il ciclo — scorrete
list(d). - Aspettarsi una copia dall'assegnazione — usate
copy()odeepcopy. {}al posto del set vuoto — è un dizionario vuoto.
Conclusioni
Il dict è la struttura dati principale di Python: ricerca rapida per chiave, ordine di inserimento conservato, metodi comodi come get, setdefault e update, cicli tramite items(), ordinamento con sorted e unione con l'operatore |. Fate attenzione alla copia e alle chiavi hashable, e per le impostazioni nella Python 3.15 usate frozendict. Dizionari, liste, tuple e set sono trattati in pratica nel corso «Python 3.15: dal principiante al professionista», sull'esempio di un tracker delle spese.
Domande frequenti
Si possono mettere nel dizionario valori di tipi diversi?
Sì, i valori possono essere qualsiasi cosa e differire tra loro: numeri, stringhe, liste, altri dizionari. Il vincolo riguarda solo le chiavi, che devono essere hashable.
Come si conta il numero di elementi di un dizionario?
La funzione len(d) restituisce il numero di coppie chiave — valore. Per un dizionario vuoto restituisce 0, e nell'istruzione if un dizionario vuoto viene considerato falso.
Che differenza c'è tra dict e defaultdict?
defaultdict è una sottoclasse di dict che, davanti a una chiave mancante, non solleva KeyError ma crea il valore tramite una funzione, per esempio int o list. Per il resto si comporta come un dizionario normale.
Come trovare una chiave a partire dal valore?
Non esiste un metodo diretto: serve una ricerca, per esempio next((k for k, v in d.items() if v == value), None). Se la ricerca è frequente, conviene costruire il dizionario inverso con {v: k for k, v in d.items()}.
Come salvare un dizionario su file?
Di solito con il modulo json: json.dump(d, f, ensure_ascii=False, indent=2) su un file aperto in scrittura, e json.load(f) per leggerlo. Le chiavi diventeranno stringhe.
Questo argomento è trattato in modo approfondito, con pratica, nel corso «Python 3.15: от новичка до профи».
Vai al corso