W3docs

Comprehension di dizionari e set in Python

Padroneggia le comprehension di dizionari, set ed espressioni generatrici in Python con sintassi chiara, esempi reali e insidie comuni.

Le comprehension di dizionari, le comprehension di set e le espressioni generatrici estendono la sintassi compatta della list comprehension ad altre strutture dati. Questo capitolo spiega ciascuna forma in dettaglio: sintassi, filtraggio, annidamento, pattern del mondo reale e quando evitarle.

Comprehension di dizionari

Una comprehension di dizionario costruisce un nuovo dict a partire da qualsiasi iterabile in una singola espressione. Invece di scrivere un ciclo for che chiama d[key] = value ad ogni iterazione, si descrive il mapping in modo conciso tra parentesi graffe.

Sintassi

new_dict = {key_expr: value_expr for item in iterable}

Aggiungere un filtro if opzionale dopo l'iterabile:

new_dict = {key_expr: value_expr for item in iterable if condition}
ParteRuolo
key_exprEspressione che produce ogni chiave
value_exprEspressione che produce ogni valore
itemVariabile di ciclo — assume ogni valore da iterable in sequenza
if conditionFiltro opzionale — salta gli elementi dove condition è False

Esempio di base: costruire una mappa dei quadrati

squares = {x: x ** 2 for x in range(1, 6)}
print(squares)
# {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

Il ciclo for equivalente:

squares = {}
for x in range(1, 6):
    squares[x] = x ** 2

Entrambi producono lo stesso risultato; la forma con comprehension è più concisa ed esprime l'intento a colpo d'occhio.

Filtraggio delle voci

Mantenere solo le coppie che soddisfano una condizione:

prices = {"apple": 1.20, "banana": 0.50, "orange": 0.80, "grape": 2.50}

expensive = {item: price for item, price in prices.items() if price >= 1.00}
print(expensive)
# {'apple': 1.2, 'grape': 2.5}

Trasformazione dei valori

Applicare una funzione o un'espressione aritmetica a ogni valore:

prices = {"apple": 1.20, "banana": 0.50, "orange": 0.80}

# Apply a 10% discount and round to 2 decimal places
discounted = {item: round(price * 0.9, 2) for item, price in prices.items()}
print(discounted)
# {'apple': 1.08, 'banana': 0.45, 'orange': 0.72}

Scambio di chiavi e valori

Invertire un dizionario (funziona correttamente quando tutti i valori sono univoci):

capitals = {"France": "Paris", "Germany": "Berlin", "Japan": "Tokyo"}

inverted = {city: country for country, city in capitals.items()}
print(inverted)
# {'Paris': 'France', 'Berlin': 'Germany', 'Tokyo': 'Japan'}

Se i valori non sono univoci, le voci successive sovrascrivono quelle precedenti. Usare questo approccio solo quando si è certi che i valori formino una mappatura uno a uno.

Costruire da due iterabili con zip()

zip() accoppia elementi di due sequenze, rendendo facile costruire un dizionario da liste separate di chiavi e valori:

keys = ["name", "age", "city"]
values = ["Alice", 30, "Berlin"]

profile = {k: v for k, v in zip(keys, values)}
print(profile)
# {'name': 'Alice', 'age': 30, 'city': 'Berlin'}

Questo è equivalente a dict(zip(keys, values)), ma la forma con comprehension permette di aggiungere facilmente un filtro o trasformare i valori allo stesso tempo.

Normalizzazione delle chiavi

Un'operazione comune nel mondo reale è la pulizia delle chiavi di un dizionario — ad esempio, convertire tutte le chiavi in minuscolo quando si uniscono dati da fonti diverse:

raw = {"Name": "Alice", "AGE": 30, "City": "Berlin"}

normalised = {k.lower(): v for k, v in raw.items()}
print(normalised)
# {'name': 'Alice', 'age': 30, 'city': 'Berlin'}

Espressione di valore condizionale (ternario nel valore)

È possibile usare un'espressione ternaria nella posizione del valore per scegliere tra due valori per elemento:

scores = {"Alice": 95, "Bob": 62, "Carol": 78, "Dave": 45}

grades = {name: "pass" if score >= 70 else "fail" for name, score in scores.items()}
print(grades)
# {'Alice': 'pass', 'Bob': 'fail', 'Carol': 'pass', 'Dave': 'fail'}

Comprehension di dizionari annidate

È possibile annidare una comprehension per gestire dati su più livelli. Ad esempio, estrarre un campo da una lista di dizionari annidati:

students = [
    {"name": "Alice", "score": 95, "grade": "A"},
    {"name": "Bob",   "score": 82, "grade": "B"},
    {"name": "Carol", "score": 91, "grade": "A"},
]

# Build a {name: score} mapping from the list
name_to_score = {s["name"]: s["score"] for s in students}
print(name_to_score)
# {'Alice': 95, 'Bob': 82, 'Carol': 91}

Mantenere l'annidamento superficiale. Se la logica diventa difficile da seguire, estrarre una funzione di supporto o usare un semplice ciclo for.

Comprehension di set

Una comprehension di set costruisce un set in una singola espressione. Poiché i set contengono solo elementi univoci, i duplicati vengono rimossi automaticamente.

Sintassi

new_set = {expression for item in iterable}
new_set = {expression for item in iterable if condition}

L'unica differenza visiva rispetto a una comprehension di dizionario è l'assenza dei due punti — c'è una sola espressione, non una coppia key: value.

Esempio di base: quadrati univoci

numbers = [1, 2, 2, 3, 3, 3, 4]
unique_squares = {x ** 2 for x in numbers}
print(unique_squares)
# {1, 4, 9, 16}

L'ordine dell'output non è garantito — i set non sono ordinati, quindi non fare affidamento su un ordine di visualizzazione specifico.

Deduplicazione durante la trasformazione

Un pattern comune è normalizzare le stringhe e deduplicarle in un unico passaggio:

tags = ["Python", "python", "PYTHON", "Data", "data", "DATA"]

unique_tags = {tag.lower() for tag in tags}
print(unique_tags)
# {'python', 'data'}

Filtraggio con una condizione

words = ["cat", "elephant", "dog", "rhinoceros", "ant"]

long_words = {w for w in words if len(w) > 4}
print(long_words)
# {'elephant', 'rhinoceros'}

Trovare elementi comuni

Le comprehension di set si combinano naturalmente con le operazioni sugli insiemi:

list_a = [1, 2, 3, 4, 5, 5, 6]
list_b = [4, 5, 6, 7, 8]

set_a = {x for x in list_a}
set_b = {x for x in list_b}

common = set_a & set_b
print(common)
# {4, 5, 6}

Per una conversione semplice senza trasformazione, set(list_a) è più diretto. Usare una comprehension di set quando è necessario filtrare o trasformare durante la conversione.

Espressioni generatrici

Un'espressione generatrice ha l'aspetto di una list comprehension ma utilizza le parentesi tonde invece di quelle quadre. Invece di costruire l'intera collezione in memoria in una volta sola, produce un valore alla volta, su richiesta (in modo pigro).

Sintassi

gen = (expression for item in iterable)
gen = (expression for item in iterable if condition)

Perché usare un'espressione generatrice?

ScenarioList comprehensionEspressione generatrice
È necessario iterare il risultato più volteNo — i generatori si esauriscono dopo un singolo passaggio
È necessario l'accesso casuale per indice (result[3])No
Il risultato viene passato a sum(), max(), any(), ecc.Funziona ma alloca una listaPreferita — trasmette i valori senza costruire una lista
Sequenza molto grande o infinitaPuò esaurire la memoriaEfficiente — un valore alla volta

Esempio di base

# List comprehension — builds the entire list in memory
squares_list = [x ** 2 for x in range(1, 6)]
print(squares_list)   # [1, 4, 9, 16, 25]

# Generator expression — yields values one at a time
squares_gen = (x ** 2 for x in range(1, 6))
print(squares_gen)    # <generator object <genexpr> at 0x...>
print(list(squares_gen))  # [1, 4, 9, 16, 25]

L'oggetto generatore in sé non è la lista — lo si consuma iterandolo o passandolo a una funzione.

Passaggio diretto a funzioni integrate

Quando si passa un'espressione generatrice come unico argomento a una funzione, è possibile omettere la coppia extra di parentesi:

total = sum(x ** 2 for x in range(1, 1001))
print(total)  # 333833500

maximum = max(len(word) for word in ["apple", "banana", "kiwi"])
print(maximum)  # 6

any_negative = any(x < 0 for x in [1, -2, 3])
print(any_negative)  # True

Vantaggio in termini di memoria

Per grandi quantità di dati, un'espressione generatrice evita di caricare tutto in RAM:

# Simulate a large log file as a list of strings
log_lines = [f"ERROR line {i}" if i % 100 == 0 else f"INFO line {i}" for i in range(1_000_000)]

# Generator scans lines without building an intermediate list
error_count = sum(1 for line in log_lines if line.startswith("ERROR"))
print(error_count)  # 10000

I generatori si esauriscono dopo un singolo passaggio

Questa è l'insidia più comune:

gen = (x * 2 for x in range(5))

print(list(gen))  # [0, 2, 4, 6, 8]
print(list(gen))  # [] — the generator is now empty

Se è necessario iterare il risultato più di una volta, usare una list comprehension oppure ricreare il generatore.

Concatenazione di espressioni generatrici

Le espressioni generatrici possono essere composte senza creare liste intermedie. Ogni stadio preleva i valori dallo stadio precedente:

numbers = range(1, 11)

# Stage 1: filter even numbers
evens = (x for x in numbers if x % 2 == 0)

# Stage 2: square them
even_squares = (x ** 2 for x in evens)

print(list(even_squares))  # [4, 16, 36, 64, 100]

Non viene creata alcuna lista intermedia — i valori scorrono attraverso la pipeline un valore alla volta.

Scegliere la forma giusta

Hai bisogno di…Usa
Una lista di valori trasformati/filtrati[expr for item in it]
Un dizionario da coppie di valori{k: v for item in it}
Una collezione senza duplicati{expr for item in it}
Iterazione singola efficiente in memoria(expr for item in it)
Logica complessa e multi-istruzione per elementoCiclo for semplice

Insidie comuni

Comprehension di dizionario vs. comprehension di set. Entrambe usano le parentesi graffe {}. La differenza sta nel fatto che si scriva key: value (dict) o una singola expression (set). Un {} vuoto crea sempre un dict vuoto, non un set vuoto — usare set() per un set vuoto.

d = {}     # empty dict
s = set()  # empty set — NOT {}

Chiavi sovrascritte. Se l'espressione della chiave produce duplicati, i valori successivi sovrascrivono silenziosamente quelli precedenti:

data = [("a", 1), ("b", 2), ("a", 99)]
d = {k: v for k, v in data}
print(d)  # {'a': 99, 'b': 2} — first 'a' is gone

Scope delle variabili. In Python 3, la variabile di ciclo in qualsiasi comprehension è locale alla comprehension e non si propaga nell'ambito circostante:

x = "original"
result = {x: x.upper() for x in ["a", "b", "c"]}
print(x)  # 'original' — comprehension's x did not overwrite this

Limite di leggibilità. Se si ha bisogno di più di una condizione if o l'espressione è lunga, un ciclo for semplice con nomi di variabili descrittivi è solitamente più chiaro:

# Hard to read
result = {k: v for k, v in data.items() if k.startswith("user_") if v is not None}

# Easier to read
result = {}
for k, v in data.items():
    if k.startswith("user_") and v is not None:
        result[k] = v

Argomenti correlati

Esercitazione

Pratica
Which of the following statements about Python comprehensions is correct?
Which of the following statements about Python comprehensions is correct?
Was this page helpful?