# Dizionari in Python (dict): creazione, metodi, cicli, ordinamento e frozendict

URL: https://skillaria.top/it/guides/python/dizionari-python

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:

- `frozendict` eredita da `object`, non da `dict`, quindi `isinstance(cfg, dict)` restituisce `False`. Per verificare «qualsiasi dizionario» usate `collections.abc.Mapping` oppure `isinstance(x, (dict, frozendict))`.
- È hashable solo se lo sono chiavi e valori: `frozendict(a=[1])` si crea, ma `hash()` dà errore. L'immutabilità è superficiale, come quella della tupla.
- L'ordine viene conservato, ma `==` non lo considera.
- `json.dumps`, `pickle` e `copy` gestiscono il frozendict.

## Errori tipici

- **KeyError** in lettura — usate `get` oppure un controllo con `in`.
- **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()` o `deepcopy`.
- **`{}` 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.

## FAQ

**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.


---
Source: Skillaria.Top (https://skillaria.top)
