Meteora Web

List Comprehension, Generatori e Iteratori in Python: Guida Operativa

Se scrivi Python e usi ancora i cicli for per costruire liste o elaborare dati, stai sprecando risorse. Lo vediamo nei progetti che ci arrivano: codice più lento, memoria occupata inutilmente, carichi...

Meteora Web Redazione 5 min di lettura Read in English
List Comprehension, Generatori e Iteratori in Python: Guida Operativa

Se scrivi Python e usi ancora i cicli for per costruire liste o elaborare dati, stai sprecando risorse. Lo vediamo nei progetti che ci arrivano: codice più lento, memoria occupata inutilmente, carichi server che lievitano senza motivo. Noi, di Meteora Web, ragioniamo in termini di efficienza e costo. Una list comprehension ben scritta non è solo più elegante: consuma meno cicli CPU e rende il codice manutenibile. Un generatore, invece di caricare tutto in RAM, può ridurre l'uso di memoria del 90% su dataset reali. Questa guida ti porta dritto al punto: capire come funzionano, quando usarli e quando no, con esempi che puoi copiare e testare subito.

List Comprehension: sintassi, potenza e limiti

La list comprehension è il modo Pythonico per creare liste in una riga. Sembra magia, ma è solo sintassi compatta. La struttura base è [espressione for elemento in iterabile if condizione]. L'ordine è lo stesso di un ciclo annidato: prima la for, poi eventuale if.

Esempio pratico: filtrare ordini

Immagina di avere una lista di ordini e volere solo quelli sopra i 100€. Con un ciclo classico:

ordini = [45, 120, 80, 200, 55]
ordini_filtrati = []
for prezzo in ordini:
    if prezzo > 100:
        ordini_filtrati.append(prezzo)
print(ordini_filtrati)  # [120, 200]

Con list comprehension:

ordini_filtrati = [prezzo for prezzo in ordini if prezzo > 100]
print(ordini_filtrati)  # [120, 200]

Meno righe, stessa leggibilità. Ma attenzione: non esagerare. Se la condizione è complessa o devi annidare tre for, meglio un ciclo normale. La leggibilità conta più della brevità.

Operazioni su ogni elemento

Puoi trasformare i dati al volo. Esempio: applicare IVA del 22% a una lista di prezzi netti:

netti = [10, 25, 50]
con_iva = [round(p * 1.22, 2) for p in netti]
print(con_iva)  # [12.2, 30.5, 61.0]

List comprehension nidificate

Funzionano come cicli annidati. Per appiattire una matrice:

matrice = [[1,2,3], [4,5], [6]]
appiattita = [num for riga in matrice for num in riga]
print(appiattita)  # [1, 2, 3, 4, 5, 6]

Attenzione all'ordine: prima il ciclo esterno, poi quello interno. Leggi da sinistra a destra come fossero annidati.

Generatori: quando la memoria è un problema

Una list comprehension costruisce l'intera lista in memoria. Se hai un milione di elementi, occupi diversi MB di RAM. Un generatore, invece, produce un elemento alla volta – lazy evaluation. La sintassi è identica, ma con parentesi tonde invece di quadre.

numeri = range(1_000_000)
# Generatore: non occupa memoria
gen = (x * 2 for x in numeri)
print(next(gen))  # 0
print(next(gen))  # 2

Non puoi indicizzare un generatore né conoscerne la lunghezza senza esaurirlo. Ma puoi iterarci sopra con un for o convertirlo in lista (perdendo il vantaggio).

Yield: generatori fatti su misura

Con yield crei funzioni generatrici. Esempio: leggere un file di log riga per riga senza caricarlo tutto in RAM:

def leggi_log(path):
    with open(path, 'r') as f:
        for riga in f:
            yield riga.strip()

for entry in leggi_log('server.log'):
    if 'ERROR' in entry:
        print(entry)

Ogni chiamata a next() riprende l'esecuzione dal punto in cui si era fermata. È come un ciclo congelato. Utile per pipeline di dati o flussi infiniti.

Iteratori: il motore sotto il cofano

List comprehension, generatori, cicli for – tutto si basa sugli iteratori. Un iterabile è un oggetto che restituisce un iteratore tramite iter(). L'iteratore implementa __next__() e solleva StopIteration quando finisce.

numeri = [1, 2, 3]
it = iter(numeri)
print(next(it))  # 1
print(next(it))  # 2
print(next(it))  # 3
# print(next(it))  # StopIteration

Puoi creare iteratori personalizzati definendo __iter__ e __next__. Esempio: un contatore che parte da un valore e si ferma a un limite:

class Contatore:
    def __init__(self, start, stop):
        self.current = start
        self.stop = stop
    def __iter__(self):
        return self
    def __next__(self):
        if self.current > self.stop:
            raise StopIteration
        valore = self.current
        self.current += 1
        return valore

for num in Contatore(1, 5):
    print(num)  # 1 2 3 4 5

I generatori sono un modo più semplice per scrivere iteratori, ma capire il meccanismo ti permette di debuggarli e ottimizzarli.

Confronto performance: list comprehension vs generatori

Noi misuriamo sempre. Con il modulo timeit puoi testare:

import timeit

# List comprehension: 1 milione di quadrati
list_time = timeit.timeit('[x2 for x in range(1_000_000)]', number=1)
print(f"List comprehension: {list_time:.4f} sec")  # ~0.06 sec

# Generatore: stesso calcolo, ma lazy
gen_time = timeit.timeit('(x2 for x in range(1_000_000))', number=1)
print(f"Generatore: {gen_time:.4f} sec")  # ~0.000001 sec (non esegue!)

# Se poi iteriamo il generatore
gen = (x**2 for x in range(1_000_000))
def consuma():
    for _ in gen:
        pass
gen_iter_time = timeit.timeit('consuma()', globals={'consuma': consuma}, number=1)
print(f"Generatore iterato: {gen_iter_time:.4f} sec")  # ~0.08 sec

Morale: la creazione di un generatore è istantanea, ma l'iterazione totale è simile alla list comprehension. Il vero guadagno è quando non devi consumare tutto. Ad esempio, se cerchi il primo elemento che soddisfa una condizione, un generatore si ferma subito.

Errori comuni e come evitarli

  • Usare list comprehension quando serve un generatore: se devi passare i dati a una funzione che accetta un iterabile (es. sum(), any(), max()), non serve creare una lista intermedia. Usa un generatore: sum(x**2 for x in range(10)).
  • Riutilizzare un generatore: dopo averlo esaurito, è vuoto. Se devi iterare due volte, convertilo in lista (o rinegociałlo).
  • Complessità eccessiva nelle list comprehension: se hai più di due for o condizioni, scrivi un ciclo normale con commenti. Il codice deve essere capito da altri (o da te tra 6 mesi).
  • Ignorare lo scope delle variabili: nelle list comprehension, la variabile del ciclo “perde” nel namespace (Python 2) o è locale (Python 3). In ogni caso, evita di modificare variabili esterne dentro una comprehension.

Una checklist per decidere

  1. Devo produrre una lista? → List comprehension.
  2. Devo processare un flusso di dati uno alla volta? → Generatore.
  3. Voglio solo iterare una volta e fermarmi presto? → Generatore.
  4. La logica è complessa? → Ciclo normale o funzione generatrice con yield.
  5. La leggibilità è prioritaria? → Scrivi nel modo più chiaro, anche se più lungo.

In sintesi — cosa fare adesso

Subito. Rivedi il tuo codice Python: cerca cicli for che popolano liste. Sostituisci con list comprehension dove è lineare. Dove possibile, trasforma in generatori per ridurre memoria. Testa le performance con timeit. Impara a leggere gli iterator protocol – è la base delle librerie asincrone e dei framework web. Noi, di Meteora Web, usiamo questi concetti ogni giorno nei nostri progetti Laravel e Python per gestire flussi di dati in modo efficiente, anche su server condivisi. Il risparmio di risorse si traduce in costi inferiori e clienti più soddisfatti.

Se vuoi approfondire l'uso degli iteratori in contesti reali, dai un’occhiata alla documentazione ufficiale Python su iteratori.

Zenithby Meteora Web Il sistema operativo della tua attività. Social, clienti, prenotazioni e fatture in un'unica piattaforma. Palestre, barber, professionisti. Scopri Zenith → Demo gratis · senza carta

Leggi anche

Dal journal

Ultime guide