Comprensioni di lista e generatori per costruire pipeline di dati pulite in Python

by theArchitect
SHARE
Comprensioni di lista e generatori per costruire pipeline di dati pulite in Python
© Guida-HTML5.it

Introduzione

Le comprensioni di lista e i generatori sono due strumenti fondamentali di Python per scrivere codice compatto, leggibile ed efficiente. Spesso vengono presentati solo come “versioni più eleganti dei cicli for”, ma in realtà sono molto più utili quando devi costruire una pipeline di elaborazione dati: leggere, filtrare, trasformare e aggregare informazioni in più passaggi senza appesantire il codice.

Questo tutorial si concentra su un sotto-argomento pratico e spesso sottovalutato: come usare comprensioni e generatori per costruire pipeline di dati. È un approccio molto utile quando lavori con file di log, record CSV, dati provenienti da API o liste di oggetti, perché ti permette di separare chiaramente i passaggi di elaborazione e di ridurre il consumo di memoria quando serve.

Vedremo un esempio realistico: partiamo da una lista di eventi applicativi grezzi, poi li puliamo, filtriamo, trasformiamo e infine calcoliamo alcune metriche. L’obiettivo non è solo “scrivere meno righe”, ma scrivere meglio.

Codice completo

from datetime import datetime

# Dati grezzi: eventi provenienti da un log applicativo
raw_events = [
    {"timestamp": "2026-01-10 09:15:00", "user": "alice", "action": "login", "status": "ok", "duration_ms": "120"},
    {"timestamp": "2026-01-10 09:16:10", "user": "bob", "action": "search", "status": "ok", "duration_ms": "45"},
    {"timestamp": "2026-01-10 09:17:30", "user": "alice", "action": "purchase", "status": "ok", "duration_ms": "310"},
    {"timestamp": "2026-01-10 09:18:05", "user": "carol", "action": "login", "status": "error", "duration_ms": "90"},
    {"timestamp": "2026-01-10 09:19:20", "user": "bob", "action": "purchase", "status": "ok", "duration_ms": "280"},
    {"timestamp": "2026-01-10 09:20:00", "user": "dave", "action": "search", "status": "ok", "duration_ms": "35"},
    {"timestamp": "2026-01-10 09:21:15", "user": "alice", "action": "search", "status": "ok", "duration_ms": "40"},
]

# 1) Comprensione di lista: pulizia e normalizzazione
# Convertiamo le stringhe in tipi più utili e uniformiamo i campi
clean_events = [
    {
        "timestamp": datetime.strptime(event["timestamp"], "%Y-%m-%d %H:%M:%S"),
        "user": event["user"].strip().lower(),
        "action": event["action"].strip().lower(),
        "status": event["status"].strip().lower(),
        "duration_ms": int(event["duration_ms"]),
    }
    for event in raw_events
]

# 2) Generatore: filtriamo solo gli eventi validi
# Usiamo una funzione generatrice per evitare di creare liste intermedie inutili
def valid_events(events):
    for event in events:
        if event["status"] == "ok" and event["duration_ms"] >= 50:
            yield event

# 3) Altra pipeline con generatori: selezioniamo solo gli acquisti
purchase_events = (
    event for event in valid_events(clean_events)
    if event["action"] == "purchase"
)

# 4) Estrazione dei valori con comprensione di lista
purchase_durations = [event["duration_ms"] for event in purchase_events]

# 5) Calcolo di metriche semplici
total_purchases = len(purchase_durations)
average_duration = sum(purchase_durations) / total_purchases if total_purchases else 0

# 6) Raggruppamento per utente con una comprensione di dizionario
users = sorted({event["user"] for event in clean_events})
purchase_count_by_user = {
    user: sum(1 for event in clean_events if event["user"] == user and event["action"] == "purchase" and event["status"] == "ok")
    for user in users
}

# 7) Report finale
print("Eventi puliti:")
for event in clean_events:
    print(event)

print("nDurate acquisti validi:", purchase_durations)
print("Numero acquisti validi:", total_purchases)
print("Durata media acquisti validi:", round(average_duration, 2))
print("Acquisti per utente:", purchase_count_by_user)

Spiegazione

Il codice mostra una mini-pipeline composta da più fasi. Ogni fase usa lo strumento più adatto: comprensioni di lista quando vuoi creare una collezione concreta, generatori quando vuoi produrre elementi uno alla volta.

1. Pulizia dei dati con una comprensione di lista

La variabile clean_events nasce da una comprensione di lista. Qui trasformiamo ogni evento grezzo in un dizionario più utile:

  • il timestamp viene convertito in un oggetto datetime;
  • le stringhe vengono normalizzate con strip() e lower();
  • la durata, inizialmente testuale, diventa un intero.

Questo passaggio è perfetto per una comprensione di lista perché vogliamo davvero ottenere una lista completa di record puliti da riutilizzare più volte.

2. Filtro lazily valutato con un generatore

La funzione valid_events() usa yield per produrre gli eventi validi uno alla volta. Questo è un vantaggio importante quando i dati sono tanti: non serve costruire subito una lista intermedia, perché ogni evento viene restituito solo quando richiesto.

La condizione applicata è semplice ma realistica: consideriamo validi solo gli eventi con stato ok e durata almeno di 50 ms. In un progetto reale, questo filtro potrebbe includere controlli su campi mancanti, valori fuori range o record duplicati.

3. Combinare generatori e comprensioni

La variabile purchase_events è un generatore espresso, cioè una sintassi simile alla comprensione di lista ma con parentesi tonde. Anche qui i dati non vengono materializzati subito.

Subito dopo, però, facciamo una scelta consapevole: trasformiamo il generatore in una lista con purchase_durations. Perché? Perché vogliamo calcolare len() e sum(), e queste operazioni sono più comode su una lista già pronta.

Questa è una regola pratica molto utile: usa il generatore finché puoi, materializza la lista solo quando ti serve davvero.

4. Aggregazione con espressioni compatte

Il calcolo di average_duration mostra una best practice importante: gestire il caso in cui la lista sia vuota. Se non ci sono acquisti validi, il denominatore sarebbe zero, quindi restituiamo 0 in modo sicuro.

Inoltre, il dizionario purchase_count_by_user usa una comprensione di dizionario con una sum() interna. È una forma elegante per produrre un report sintetico per utente.

5. Quando preferire lista e quando generatore

In generale:

  • comprensione di lista: quando ti serve una collezione da riutilizzare, indicizzare o attraversare più volte;
  • generatore: quando vuoi elaborare dati grandi, o quando il risultato serve solo una volta;
  • generatore espresso: ottimo per concatenare passaggi semplici in modo compatto;
  • funzione con yield: migliore quando la logica di generazione è più articolata e vuoi mantenerla leggibile.

Best practice

  • Non abusare delle comprensioni annidate: se la logica diventa difficile da leggere, meglio un ciclo for esplicito o una funzione dedicata.
  • Usa i generatori per pipeline lunghe: quando i dati sono grandi, evitare liste intermedie può ridurre molto il consumo di memoria.
  • Separa pulizia, filtro e aggregazione: ogni fase dovrebbe avere uno scopo chiaro. Questo rende il codice più testabile.
  • Materializza solo quando serve: converti un generatore in lista solo se ti servono operazioni come len(), indicizzazione o riutilizzo multiplo.
  • Attenzione ai generatori consumati: un generatore si esaurisce dopo l’uso. Se devi iterarlo più volte, crea una nuova pipeline o salva i dati in una lista.
  • Rendi espliciti i nomi: variabili come clean_events, valid_events e purchase_durations comunicano meglio l’intento rispetto a nomi generici.

Riepilogo

Comprensioni di lista e generatori non sono solo scorciatoie sintattiche: sono strumenti complementari per costruire pipeline di dati efficaci in Python. Le comprensioni di lista sono ideali per creare collezioni pronte all’uso, mentre i generatori permettono di elaborare i dati in modo lazy, con meno memoria e maggiore flessibilità.

Nel nostro esempio abbiamo visto un flusso completo:

  • pulizia dei record con una comprensione di lista;
  • filtraggio progressivo con una funzione generatrice;
  • ulteriore selezione con un generatore espresso;
  • aggregazione finale con liste e dizionari comprensivi.

Il vero valore di questi strumenti emerge quando li usi insieme, in modo coerente. Se impari a pensare in termini di pipeline, il tuo codice diventa più chiaro, più modulare e più facile da mantenere.

Approfondisci con risorse ufficiali

  • Python Tutorial - Data Structures: documentazione ufficiale sulle strutture dati e sulle comprensioni.
  • Python Language Reference - Expressions: riferimento ufficiale per comprensioni di lista, set, dizionario e generator expression.
  • itertools: modulo standard molto utile per costruire pipeline lazy e combinare generatori.
  • datetime: documentazione ufficiale per lavorare con date e orari nei dati reali.

Se vuoi fare un passo in più, prova a riscrivere l’esempio usando dati letti da un file CSV o da una risposta API: vedrai subito quanto comprensioni e generatori siano pratici in scenari reali.

SHARE