Cosa sono i generator in Python?
Alla base, i generator in Python sono un tipo speciale di funzione o anche un’espressione compatta che produce una sequenza di valori in modo “pigro” (lazy). Pensa ai generator come a un nastro trasportatore in fabbrica: invece di impilare tutti i prodotti in un punto e rimanere senza spazio, elabori ogni elemento man mano che arriva. Questo rende i generator efficienti in termini di memoria e una naturale estensione del protocollo degli iterator di Python, alla base di molti strumenti integrati come i cicli for e le comprensioni.
La magia dei generator sta nella keyword yield. A differenza di return, che restituisce un singolo valore ed esce dalla funzione, yield produce un valore, mette in pausa l’esecuzione della funzione e ne salva lo stato. Quando il generator viene richiamato, riprende da dove si era fermato.
Per esempio, immagina di leggere un file di log enorme riga per riga. Un generator può elaborare ogni riga mentre viene letta senza caricare l’intero file in memoria. Questa "valutazione pigra" distingue i generator dalle funzioni tradizionali e li rende uno strumento privilegiato per i task sensibili alle prestazioni.
Un esempio di base di generator in Python
Facciamo un po’ di pratica per prendere confidenza con l’idea. Ecco una funzione generator che produce i primi n interi.
def generate_integers(n):
for i in range(n):
yield i # Pauses here and returns i
# Using the generator
for num in generate_integers(5):
print(num)
0
1
2
3
4
Ho creato un visual per aiutarti a vedere cosa succede dietro le quinte:

Sintassi e pattern dei generator in Python
I generator possono essere implementati in vari modi. Detto ciò, i due principali sono: funzioni generator ed espressioni generator.
Funzioni generator
Una funzione generator è definita come una normale funzione ma usa la keyword yield invece di return. Quando viene chiamata, restituisce un oggetto generator su cui si può iterare.
def count_up_to(n):
count = 1
while count <= n:
yield count
count += 1
# Using the generator
counter = count_up_to(5)
for num in counter:
print(num)
1
2
3
4
5
Dall’esempio sopra, vediamo che quando la funzione count_up_to viene chiamata, restituisce un oggetto generator. Ogni volta che il ciclo for richiede un valore, la funzione viene eseguita fino a incontrare yield, producendo il valore corrente di count e preservando il suo stato tra le iterazioni per poter riprendere esattamente da dove si era fermata.
Espressioni generator
Le espressioni generator sono un modo compatto per creare generator. Sono simili alle list comprehension ma con le parentesi tonde invece delle quadre.
# List comprehension (eager evaluation)
squares_list = [x**2 for x in range(5)] # [0, 1, 4, 9, 16]
# Generator expression (lazy evaluation)
squares_gen = (x**2 for x in range(5))
# Using the generator
for square in squares_gen:
print(square)
0
1
4
9
16
Quindi, qual è la differenza tra una list comprehension e un’espressione generator? La list comprehension crea l’intera lista in memoria, mentre l’espressione generator produce i valori uno alla volta, risparmiando memoria. Se non conosci le list comprehension, puoi leggerne nel nostro Python List Comprehension Tutorial.
Generator vs iterator in Python
Gli iterator tradizionali in Python richiedevano classi con metodi espliciti __iter__() e __next__(), con molto boilerplate e gestione manuale dello stato, mentre le funzioni generator semplificano il processo preservando automaticamente lo stato ed eliminando la necessità di questi metodi—come dimostra una semplice funzione che produce il quadrato di ciascun numero fino a n.
Perché usiamo i generator in Python
Spiegando cosa sono i generator in Python, ho anche introdotto il perché vengono usati. In questa sezione voglio entrare un po’ più nel dettaglio. Perché i generator non sono solo una feature elegante di Python: risolvono davvero problemi concreti.
Efficienza della memoria
A differenza di liste o array, che memorizzano contemporaneamente tutti i loro elementi in memoria, i generator producono i valori al volo e tengono in memoria un solo elemento alla volta.
Per esempio, considera la differenza tra range() e xrange() in Python 2:
-
range()creava una lista in memoria, cosa problematica per range molto grandi. -
xrange()si comportava come un generator, producendo i valori in modo pigro.
Dato che il comportamento di xrange() era più utile, ora, in Python 3, anche range() si comporta come un generator, evitando l’overhead di memoria di memorizzare tutti i valori contemporaneamente.
Per mostrare l’idea, confrontiamo l’uso di memoria generando una sequenza di 10 milioni di numeri:
import sys
# Using a list
numbers_list = [x for x in range(10_000_000)]
print(f"Memory used by list: {sys.getsizeof(numbers_list) / 1_000_000:.2f} MB")
# Using a generator
numbers_gen = (x for x in range(10_000_000))
print(f"Memory used by generator: {sys.getsizeof(numbers_gen)} bytes")
Memory used by list: 89.48 MB
Memory used by the generator: 112 bytes
Come vedi, il generator usa quasi niente memoria rispetto alla lista, e la differenza è notevole.
Miglioramenti delle prestazioni
Grazie alla valutazione pigra, i valori vengono calcolati solo quando servono. Questo significa che puoi iniziare a elaborare i dati subito, senza aspettare che l’intera sequenza sia generata.
Per esempio, immagina di sommare i quadrati del primo milione di numeri:
# Using a list (eager evaluation)
sum_of_squares_list = sum([x**2 for x in range(1_000_000)])
# Using a generator (lazy evaluation)
sum_of_squares_gen = sum(x**2 for x in range(1_000_000))
Anche se entrambi gli approcci danno lo stesso risultato, la versione con il generator evita di creare una lista enorme, quindi il risultato arriva più in fretta.
Semplicità e leggibilità
I generator semplificano l’implementazione degli iterator eliminando il boilerplate. Confronta un iterator basato su classe con una funzione generator:
Ecco l’iterator basato su classe:
class SquaresIterator:
def __init__(self, n):
self.n = n
self.current = 0
def __iter__(self):
return self
def __next__(self):
if self.current >= self.n:
raise StopIteration
result = self.current ** 2
self.current += 1
return result
# Usage
squares = SquaresIterator(5)
for square in squares:
print(square)
Ecco la funzione generator:
def squares_generator(n):
for i in range(n):
yield i ** 2
# Usage
squares = squares_generator(5)
for square in squares:
print(square)
La versione con il generator è più breve, più leggibile e non richiede boilerplate. È un perfetto esempio della filosofia di Python: semplice è meglio.
Gestire sequenze infinite
Infine, va detto che i generator sono particolarmente adatti a rappresentare sequenze infinite, cosa impossibile con le liste. Per esempio, considera la sequenza di Fibonacci:
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
# Usage
fib = fibonacci()
for _ in range(10):
print(next(fib))
0
1
1
2
3
5
8
13
21
34
Questo generator può produrre numeri di Fibonacci all’infinito senza esaurire la memoria. Altri esempi includono l’elaborazione di flussi di dati live o il lavoro con dati di serie temporali.
Concetti avanzati sui generator in Python
Ora vediamo idee un po’ più difficili. In questa sezione esploreremo come comporre i generator e usare metodi specifici come .send(), .throw() e .close().
Collegare i generator tra loro
I generator possono essere combinati. Puoi trasformare, filtrare ed elaborare i dati in modo modulare concatenando più generator.
Poniamo di avere una sequenza infinita di numeri e di voler elevare ogni numero al quadrato e filtrare i risultati dispari:
def infinite_sequence():
num = 0
while True:
yield num
num += 1
def square_numbers(sequence):
for num in sequence:
yield num ** 2
def filter_evens(sequence):
for num in sequence:
if num % 2 == 0:
yield num
# Compose the generators
numbers = infinite_sequence()
squared = square_numbers(numbers)
evens = filter_evens(squared)
# Print the first 10 even squares
for _ in range(10):
print(next(evens))
0
4
16
36
64
100
144
196
256
324
Il processo prevede che la funzione infinite_sequence generi numeri indefinitamente, mentre square_numbers produce il quadrato di ciascun numero e poi filter_evens filtra i numeri dispari per ottenere solo quadrati pari. Il nostro percorso professionale Associate Python Developer approfondisce questi temi, così puoi vedere come costruire e fare debug di pipeline complesse usando generator, oltre a iterator e list comprehension.
Metodi speciali dei generator
I generator includono metodi avanzati che permettono comunicazione bidirezionale e terminazione controllata.
send()
Il metodo .send() ti permette di passare valori al generator, trasformandolo in una coroutine. È utile per creare generator interattivi o con stato.
def accumulator():
total = 0
while True:
value = yield total
if value is not None:
total += value
# Using the generator
acc = accumulator()
next(acc) # Start the generator
print(acc.send(10)) # Output: 10
print(acc.send(5)) # Output: 15
print(acc.send(20)) # Output: 35
Ecco come funziona:
-
Il generator parte con
next(acc)per inizializzarlo. -
Ogni chiamata a
.send(value)passa un valore al generator, che viene assegnato avaluenell’istruzioneyield. -
Il generator aggiorna il suo stato (
total) e produce il nuovo risultato.
throw()
Il metodo .throw() permette di sollevare un’eccezione all’interno del generator, utile per la gestione degli errori o per segnalare condizioni specifiche.
def resilient_generator():
try:
for i in range(5):
yield i
except ValueError:
yield "Error occurred!"
# Using the generator
gen = resilient_generator()
print(next(gen)) # Output: 0
print(next(gen)) # Output: 1
print(gen.throw(ValueError)) # Output: "Error occurred!"
Ecco come funziona questo:
-
Il generator gira normalmente finché non viene chiamato
.throw(). -
L’eccezione viene sollevata all’interno del generator, che può gestirla con un blocco
try-except.
close()
Il metodo .close() interrompe un generator sollevando un’eccezione GeneratorExit. È utile per ripulire le risorse o fermare generator infiniti.
def infinite_counter():
count = 0
try:
while True:
yield count
count += 1
except GeneratorExit:
print("Generator closed!")
# Using the generator
counter = infinite_counter()
print(next(counter)) # Output: 0
print(next(counter)) # Output: 1
counter.close() # Output: "Generator closed!"
Ecco cosa succede:
-
Il generator gira finché non viene chiamato
.close(). -
Viene sollevata l’eccezione
GeneratorExit, che consente al generator di ripulire o loggare un messaggio prima di terminare.
Applicazioni reali nella data science
Spero ti stia diventando chiaro che i generator sono utili. In questa sezione cercherò di far risaltare i casi d’uso così puoi immaginare come funzionano davvero nel lavoro di tutti i giorni.
Elaborazione di dataset di grandi dimensioni
Una delle sfide più comuni nella data science è lavorare con dataset troppo grandi per stare in memoria. I generator offrono un modo per elaborare questi dati riga per riga.
Immagina di avere un file CSV da 10 GB con dati di vendita e di dover filtrare i record per una regione specifica. Ecco come usare una pipeline di generator per farlo:
import csv
def read_large_csv(file_path):
""" Generator to read a large CSV file line by line."""
with open(file_path, mode="r") as file:
reader = csv.DictReader(file)
for row in reader:
yield row
def filter_by_region(data, region):
""" Generator to filter rows by a specific region."""
for row in data:
if row["Region"] == region:
yield row
# Generator pipeline
file_path = "sales_data.csv"
region = "North America"
data = read_large_csv(file_path)
filtered_data = filter_by_region(data, region)
# Process the filtered data
for record in filtered_data:
print(record)
Ecco cosa succede:
-
read_large_csvlegge il file riga per riga, producendo ogni riga come dizionario. -
filter_by_regionfiltra le righe in base alla regione specificata. -
La pipeline elabora i dati in modo incrementale, evitando sovraccarichi di memoria.
Questo approccio è utile nei workflow di estrazione, trasformazione e caricamento, dove i dati devono essere puliti e trasformati prima dell’analisi. Troverai questa logica nel nostro corso ETL and ELT in Python.
Streaming e pipeline
A volte i dati arrivano come un flusso continuo. Pensa a sensori, feed live o social media.
Supponiamo che tu stia lavorando con dispositivi IoT che generano letture di temperatura ogni secondo. Vuoi calcolare la temperatura media su una finestra mobile di 10 letture:
def sensor_data_stream():
"""Simulate an infinite stream of sensor data."""
import random
while True:
yield random.uniform(0, 100) # Simulate sensor data
def sliding_window_average(stream, window_size):
""" Calculate the average over a sliding window of readings."""
window = []
for value in stream:
window.append(value)
if len(window) > window_size:
window.pop(0)
if len(window) == window_size:
yield sum(window) / window_size
# Generator pipeline
sensor_stream = sensor_data_stream()
averages = sliding_window_average(sensor_stream, window_size=10)
# Print the average every second
for avg in averages:
print(f"Average temperature: {avg:.2f}")
Ecco la spiegazione:
-
sensor_data_streamsimula un flusso infinito di letture dei sensori. -
sliding_window_averagemantiene una finestra mobile con le ultime 10 letture e ne produce la media. -
La pipeline elabora i dati in tempo reale, ideale per monitoraggio e analytics.
Altri casi d’uso
I generator sono utili anche quando la dimensione dei dati è imprevedibile o quando i dati continuano ad arrivare/sono infiniti.
Web scraping
Quando fai scraping di siti web, spesso non sai quante pagine o elementi dovrai elaborare. I generator ti permettono di gestire questa imprevedibilità in modo elegante:
def scrape_website(url):
""" Generator to scrape a website page by page."""
while url:
# Simulate fetching and parsing a page
print(f"Scraping {url}")
data = f"Data from {url}"
yield data
url = get_next_page(url) # Hypothetical function to get the next page
# Usage
scraper = scrape_website("https://example.com/page1")
for data in scraper:
print(data)
Task di simulazione
Nelle simulazioni, come i metodi Monte Carlo o lo sviluppo di giochi, i generator possono rappresentare sequenze infinite o dinamiche:
def monte_carlo_simulation():
""" Generator to simulate random events for Monte Carlo analysis."""
import random
while True:
yield random.random()
# Usage
simulation = monte_carlo_simulation()
for _ in range(10):
print(next(simulation))
Benchmark di memoria e velocità
Per come funzionano, i generator eccellono negli scenari in cui l’efficienza di memoria è critica, ma (può sorprenderti) non sono sempre l’opzione più veloce. Confrontiamo generator e liste per capirne i trade-off.
Prima abbiamo mostrato come i generator fossero migliori delle liste in termini di memoria. Era la parte in cui confrontavamo l’uso di memoria generando 10 milioni di numeri. Facciamo ora un confronto di velocità diverso:
import time
# List comprehension
start_time = time.time()
sum([x**2 for x in range(1_000_000)])
print(f"List comprehension time: {time.time() - start_time:.4f} seconds")
# Generator expression
start_time = time.time()
sum(x**2 for x in range(1_000_000))
print(f"Generator expression time: {time.time() - start_time:.4f} seconds")
List comprehension time: 0.1234 seconds
Generator expression time: 0.1456 seconds
Sebbene un generator risparmi memoria, in questo caso è in realtà più lento della lista. Questo perché, per dataset più piccoli, c’è l’overhead di mettere in pausa e riprendere l’esecuzione.
La differenza di prestazioni è trascurabile per dataset piccoli, ma per dataset grandi il risparmio di memoria dei generator spesso supera la leggera penalità in velocità.
Problemi che possono emergere
Infine, vediamo alcuni errori o problemi comuni:
I generator si esauriscono
Una volta esaurito, un generator non può essere riutilizzato. Se vuoi iterarci di nuovo, devi ricrearlo.
gen = (x for x in range(5))
print(list(gen)) # Output: [0, 1, 2, 3, 4]
print(list(gen)) # Output: [] (the generator is exhausted)
La valutazione pigra può trarre in inganno
Poiché i generator producono valori su richiesta, errori o side effect potrebbero non emergere finché non si itera sul generator.
Si possono usare i generator in eccesso
Per dataset piccoli o task semplici, l’overhead di usare un generator potrebbe non valere il risparmio di memoria. Considera questo esempio in cui materializzo i dati per più iterazioni.
# Generator expression
gen = (x**2 for x in range(10))
# Materialize into a list
squares = list(gen)
# Reuse the list
print(sum(squares)) # Output: 285
print(max(squares)) # Output: 81
Scegliere quando usare i generator
Per ricapitolare, ecco alcune regole molto generali su quando usare i generator. Da usare per:
- Dataset grandi: Usa i generator quando lavori con dataset troppo grandi per stare in memoria.
- Sequenze infinite: Usa i generator per rappresentare sequenze infinite, come flussi di dati live o simulazioni.
- Pipeline: Usa i generator per costruire pipeline modulari di elaborazione dati che trasformano e filtrano i dati in modo incrementale.
Quando invece materializzare i dati (convertire in lista)
- Dataset piccoli: Non usare i generator se la memoria non è un problema e ti serve accesso rapido a tutti gli elementi; usa invece una lista.
- Iterazioni multiple: Non usare i generator se devi iterare più volte sugli stessi dati; materializzali in una lista per evitare di ricreare il generator.
Conclusioni e punti chiave
In tutto l’articolo abbiamo visto come i generator possano aiutarti ad affrontare sfide reali nella data science, dall’elaborazione di grandi dataset alla costruzione di pipeline di dati in tempo reale. Continua a esercitarti. Il modo migliore per padroneggiare i generator è usarli nel tuo lavoro. Per iniziare, prova a sostituire una list comprehension con un’espressione generator o a trasformare un ciclo in una funzione generator.
Una volta padroneggiate le basi, puoi esplorare argomenti nuovi e più avanzati che si basano sul concetto di generator:
-
Coroutine: Usa
.send()e.throw()per creare generator che possono ricevere ed elaborare dati, abilitando una comunicazione bidirezionale. -
Programmazione asincrona: Combina i generator con la libreria asyncio di Python per costruire applicazioni efficienti e non bloccanti.
-
Concorrenza: Scopri come i generator possono implementare il multitasking cooperativo e la concorrenza leggera.
Continua a imparare e diventa un esperto. Segui il nostro percorso professionale Python Developer o il nostro skill track Python Programming oggi stesso. Clicca sul link qui sotto per iniziare.

Technical writer specializzato in AI, ML e data science: rende idee complesse chiare e accessibili.
Domande frequenti sui generator in Python
Che cos’è esattamente un generator in Python?
Un generator in Python è un tipo speciale di funzione che usa la keyword yield per restituire un iterator, producendo i valori uno alla volta e risparmiando memoria perché non memorizza l’intera sequenza in una volta sola.
Quando dovrei usare i generator invece delle list comprehension?
I generator sono ideali per elaborare sequenze grandi o infinite quando l’efficienza di memoria è fondamentale, mentre le list comprehension vanno bene quando ti serve un elenco completo per accessi ripetuti o indicizzazione casuale.
In che modo i generator migliorano le prestazioni?
Restituendo un valore alla volta (valutazione pigra), i generator calcolano i valori al volo, riducendo l’uso di memoria e velocizzando l’elaborazione rispetto alla costruzione di intere strutture dati in memoria.
Posso iterare su un generator più di una volta?
No, i generator si esauriscono dopo un’iterazione completa. Per iterare di nuovo, devi creare una nuova istanza del generator.
Quali sono alcuni casi d’uso pratici dei generator?
I generator sono utili per elaborare grandi dataset, gestire dati in streaming in tempo reale, costruire pipeline efficienti e trattare sequenze infinite come quelle usate in algoritmi e task di simulazione.


Immagina di lavorare a un progetto di data science e di dover elaborare un dataset talmente grande che caricarlo in memoria manda in crash la macchina. Oppure di avere a che fare con una sequenza infinita, come un flusso di dati live, che non puoi certo memorizzare tutta in una volta. Queste sono le sfide che fanno correre i data scientist alla macchinetta del caffè—e a volte al tasto di reset.
In questo articolo parleremo dei generator in Python e di come usarli per semplificare il tuo codice. L’idea richiede un po’ di pratica, quindi se sei alle prime armi con Python e ti perdi leggendo, prova il nostro corso Introduction to Python per costruire basi solide.