Introduzione
Le comprensioni di lista e i generatori sono due strumenti fondamentali di Python per scrivere codice compatto, leggibile e spesso più efficiente. Molti tutorial li presentano solo come alternative “più corte” ai cicli for, ma in realtà il loro vero valore emerge quando li usiamo per costruire report di dati: riepiloghi, statistiche, elenchi filtrati e trasformazioni pronte per essere stampate, esportate o inviate a un altro componente del programma.
In questo tutorial vedremo un caso pratico: partire da una lista di ordini e creare un piccolo report testuale con:
- gli ordini validi;
- il totale per ciascun cliente;
- un elenco degli ordini con importo superiore a una soglia;
- una versione “lazy” del report basata su generatori.
Questo sotto-argomento è utile perché mostra una situazione reale: quando i dati devono essere puliti, trasformati e presentati in modo ordinato, le comprensioni di lista e i generatori aiutano a mantenere il codice semplice e modulare.
Codice completo
from collections import defaultdict
from itertools import islice
# Dati di esempio: ogni ordine è un dizionario
ordini = [
{"id": 101, "cliente": "Anna", "importo": 120.50, "stato": "pagato"},
{"id": 102, "cliente": "Luca", "importo": 75.00, "stato": "in_attesa"},
{"id": 103, "cliente": "Anna", "importo": 42.30, "stato": "pagato"},
{"id": 104, "cliente": "Marta", "importo": 210.00, "stato": "pagato"},
{"id": 105, "cliente": "Luca", "importo": 18.90, "stato": "annullato"},
{"id": 106, "cliente": "Marta", "importo": 89.99, "stato": "pagato"},
{"id": 107, "cliente": "Giulia", "importo": 150.00, "stato": "pagato"},
]
# 1) Selezioniamo solo gli ordini pagati con una comprensione di lista
ordini_pagati = [
ordine for ordine in ordini
if ordine["stato"] == "pagato"
]
# 2) Creiamo una lista di tuple (cliente, importo) per un report sintetico
righe_report = [
(ordine["cliente"], ordine["importo"])
for ordine in ordini_pagati
]
# 3) Calcoliamo il totale speso da ciascun cliente
totali_clienti = defaultdict(float)
for cliente, importo in righe_report:
totali_clienti[cliente] += importo
# 4) Creiamo una lista degli ordini "importanti"
# cioè con importo maggiore o uguale a 100 euro
ordini_importanti = [
ordine for ordine in ordini_pagati
if ordine["importo"] >= 100
]
# 5) Generatore: produciamo righe di testo una alla volta
def genera_report(ordini_validi):
yield "REPORT ORDINI PAGATI"
yield "-" * 22
for ordine in ordini_validi:
yield (
f"Ordine #{ordine[´id´]} | "
f"Cliente: {ordine[´cliente´]} | "
f"Importo: €{ordine[´importo´]:.2f}"
)
yield ""
yield "TOTALE PER CLIENTE"
yield "-" * 18
for cliente, totale in sorted(totali_clienti.items()):
yield f"{cliente}: €{totale:.2f}"
yield ""
yield "ORDINI IMPORTANTI"
yield "-" * 17
for ordine in ordini_importanti:
yield f"#{ordine[´id´]} - {ordine[´cliente´]} - €{ordine[´importo´]:.2f}"
# 6) Consumo del generatore: stampiamo solo le prime 10 righe del report
for riga in islice(genera_report(ordini_pagati), 10):
print(riga) Spiegazione
Il codice sopra combina comprensioni di lista e generatori per costruire un piccolo flusso di lavoro orientato al report.
1. Filtrare i dati con una comprensione di lista
La prima comprensione di lista seleziona solo gli ordini con stato pagato:
ordini_pagati = [
ordine for ordine in ordini
if ordine["stato"] == "pagato"
] Questa forma è utile quando vuoi esprimere in modo chiaro una trasformazione “prendi solo questi elementi”. Rispetto a un ciclo tradizionale, il codice è più compatto e il filtro è immediatamente visibile.
2. Preparare dati intermedi per il calcolo
La seconda comprensione crea una lista di tuple con solo due campi:
righe_report = [
(ordine["cliente"], ordine["importo"])
for ordine in ordini_pagati
] Qui stiamo preparando i dati per un calcolo successivo. Questo è un caso molto comune: prima si riduce la struttura ai soli campi utili, poi si aggrega.
3. Aggregare con defaultdict
Per sommare gli importi per cliente usiamo defaultdict(float). Non è una comprensione, ma si integra bene con il resto del flusso:
totali_clienti = defaultdict(float)
for cliente, importo in righe_report:
totali_clienti[cliente] += importo Il vantaggio di defaultdict(float) è che ogni nuova chiave parte automaticamente da 0.0, evitando controlli manuali.
4. Creare una seconda lista filtrata
Gli ordini “importanti” sono quelli con importo maggiore o uguale a 100 euro:
ordini_importanti = [
ordine for ordine in ordini_pagati
if ordine["importo"] >= 100
] Questo tipo di lista è molto utile nei report: puoi separare rapidamente gli elementi che meritano attenzione.
5. Usare un generatore per produrre il report
La funzione genera_report usa yield per restituire una riga alla volta:
def genera_report(ordini_validi):
yield "REPORT ORDINI PAGATI"
...
for ordine in ordini_validi:
yield f"Ordine #{ordine[´id´]} ..." Un generatore è perfetto quando non vuoi costruire subito tutta la stringa finale in memoria. Invece di creare una lista di righe e poi unirla, produci i contenuti solo quando servono.
6. Consumo parziale del generatore
Nel finale usiamo islice per stampare solo le prime 10 righe:
for riga in islice(genera_report(ordini_pagati), 10):
print(riga) Questo mostra un vantaggio concreto dei generatori: puoi leggere solo una parte del risultato senza calcolare tutto il resto. È una tecnica utile nei report lunghi, nei log o nei dataset grandi.
Best practice
- Usa le comprensioni di lista per selezionare o trasformare dati semplici, ma evita di annidare troppi
ifefornella stessa espressione: la leggibilità viene prima della sintesi. - Preferisci i generatori quando il risultato può essere consumato in streaming, ad esempio per stampare, scrivere su file o inviare a una funzione che elabora un elemento alla volta.
- Se un dato viene riutilizzato molte volte, valuta una lista invece di un generatore: i generatori si esauriscono dopo il consumo.
- Separa chiaramente filtraggio, trasformazione e aggregazione. Un report è più facile da mantenere se ogni fase ha uno scopo preciso.
- Usa nomi espliciti come
ordini_pagati,ordini_importantietotali_clienti: aiutano a capire il flusso dei dati senza leggere ogni riga nel dettaglio. - Non abusare delle comprensioni: se la logica diventa complessa, un ciclo
foresplicito può essere più chiaro e più facile da debuggare.
Riepilogo
Le comprensioni di lista e i generatori sono strumenti complementari:
- le comprensioni di lista sono ideali per filtrare e trasformare rapidamente i dati;
- i generatori sono perfetti per produrre output progressivamente, senza caricare tutto in memoria;
- insieme, permettono di costruire report puliti, modulari e facili da estendere.
Nel nostro esempio abbiamo creato un report sugli ordini partendo da dati grezzi, ottenendo una pipeline chiara: filtraggio, preparazione, aggregazione e presentazione. È un approccio molto vicino a ciò che si fa davvero in applicazioni backend, script di analisi e automazioni.
Approfondisci con risorse ufficiali
- Python Tutorial - Data Structures: documentazione ufficiale sulle strutture dati e sulle comprensioni.
- Python Tutorial - Expressions: per approfondire la sintassi delle list comprehension e delle generator expression.
- itertools: modulo ufficiale con strumenti come
islice, molto utile insieme ai generatori. - collections.defaultdict: documentazione ufficiale per aggregazioni semplici e leggibili.
Se vuoi fare un passo in più, prova a trasformare questo esempio in un report CSV o in un file di testo. È un ottimo esercizio per capire quando conviene usare una lista e quando invece un generatore è la scelta migliore.
