Generatori in Python
Questo articolo spiega i generatori in Python.
YouTube Video
Generatori in Python
Panoramica
I generatori in Python sono un tipo di iteratore e una funzionalità potente per eseguire elaborazioni ripetitive in modo efficiente. Ti permettono di scrivere codice efficiente in termini di memoria quando si lavora con grandi quantità di dati.
Cos’è un generatore?
Un generatore in Python è una funzione speciale che produce un valore alla volta, definita utilizzando la parola chiave yield
. La sua caratteristica è che mette in pausa l'esecuzione mantenendo lo stato e può riprendere in seguito.
Basi di yield
yield
è una parola chiave che restituisce un valore e mette in pausa l'esecuzione della funzione allo stesso tempo.
1def simple_generator():
2 yield 1
3 yield 2
4 yield 3
5
6gen = simple_generator()
7
8print(next(gen)) # 1
9print(next(gen)) # 2
10print(next(gen)) # 3
- Quando viene chiamata, questa funzione restituisce un oggetto generatore che produce i valori uno alla volta.
- Se chiami
next()
quando non c'è un valore successivo, si verificherà un erroreStopIteration
.
next()
e StopIteration
1def simple_generator():
2 yield 1
3 yield 2
4 yield 3
5
6gen = simple_generator()
7
8try:
9 while True:
10 value = next(gen)
11 print(value)
12except StopIteration:
13 print("Finished")
- Gestendo esplicitamente l'errore
StopIteration
in questo modo, puoi rilevare quando un generatore è terminato.
send(value)
Chiamare send(value)
riprende il generatore e invia value
alla posizione dell'espressione yield
. Il valore inviato può essere ricevuto nel generatore come valore di ritorno dell'espressione yield
. Alla prima chiamata, non puoi inviare nulla tranne None
con send(value)
, quindi devi usare next()
o send(None)
.
1def gen():
2 x = yield 1
3 print(f"x = {x}")
4 y = yield 2
5 print(f"y = {y}")
6
7g = gen()
8print(next(g)) # -> 1 (value from yield 1)
9print(g.send(10)) # -> x = 10, 2 (value from yield 2)
10print(g.send(20)) # -> y = 20, StopIteration occurs
- Con
send(10)
, ilyield
del generatore diventa un'espressione che restituisce 10, e 10 viene assegnato ax
.
throw()
Chiamare throw
riprende il generatore e solleva un'eccezione nella posizione del yield
in pausa. Puoi gestire l'eccezione all'interno del generatore per continuare l'elaborazione. Se l'eccezione non viene intercettata, si propaga all'esterno e il generatore termina.
1def gen():
2 try:
3 yield 1
4 except ValueError as e:
5 print(f"Caught: {e}")
6 yield "recovered"
7
8g = gen()
9print(next(g)) # -> 1
10print(g.throw(ValueError("boom"))) # -> Caught: boom, "recovered"
- In questo codice,
throw
viene chiamato per iniettare un'eccezione nel generatore. Dal lato del generatore, l'eccezione viene gestita e viene restituitorecovered
.
close()
Chiamare close()
termina il generatore. All'interno del generatore puoi eseguire attività di pulizia usando finally
. Chiamare next()
o send()
dopo aver chiamato close()
genera un errore di tipo StopIteration
.
1def gen():
2 try:
3 yield 1
4 finally:
5 print("Cleaning up...")
6
7g = gen()
8print(next(g)) # -> 1
9g.close() # -> Cleaning up...
- Questo codice mostra che chiamare
close()
termina il generatore e attiva il processo di pulizia infinally
.
yield from
yield from
è una sintassi usata per delegare a un subgeneratore. È un modo semplice per chiamare un altro generatore all'interno di un generatore e passare tutti i suoi valori all'ambito esterno.
1def sub_gen():
2 yield 1
3 yield 2
4
5def main_gen():
6 yield from sub_gen()
7 yield 3
8
9print(list(main_gen())) # -> [1, 2, 3]
- Questo codice delega tutti i valori dal subgeneratore al generatore esterno usando
yield from
e poi produce3
.
Relazione con gli iteratori
I generatori implementano internamente __iter__()
e __next__()
, rendendoli un tipo di iteratore. Perciò sono completamente compatibili con operazioni iterabili come i cicli for
.
Integrazione con i cicli for
In Python, un ciclo for
utilizza internamente next()
per ottenere automaticamente i valori.
1def simple_generator():
2 yield 1
3 yield 2
4 yield 3
5
6for value in simple_generator():
7 print(value)
Con questo metodo, la gestione di StopIteration
è anch'essa automatica.
Creazione di generatori infiniti
1def count_up(start=0):
2 while True:
3 yield start
4 start += 1
5
6counter = count_up()
7print(next(counter)) # 0
8print(next(counter)) # 1
È possibile creare cicli infiniti, ma occorre prestare attenzione nell'utilizzarli.
Espressioni generatrici
Le espressioni generatrici, scritte usando le parentesi tonde, consentono di definire generatori con una sintassi simile alle list comprehension.
1# List comprehension (generates the entire list at once)
2squares_list = [x**2 for x in range(5)]
3print(squares_list)
4
5# Generator expression
6squares_gen = (x**2 for x in range(5))
7for square in squares_gen:
8 print(square)
A differenza delle list comprehension, non caricano tutti gli elementi in memoria simultaneamente, risultando più efficienti nella gestione della memoria.
Gestione degli errori nei generatori
Possono verificarsi eccezioni all'interno di un generatore. In questi casi, si utilizza try-except
proprio come nel normale codice Python.
1def safe_divide_generator(numbers, divisor):
2 """Yields results of dividing numbers by a given divisor safely."""
3 for number in numbers:
4 try:
5 yield number / divisor # Attempt to divide and yield result.
6 except ZeroDivisionError:
7 yield float('inf') # Return infinity if division by zero occurs.
8
9# Example usage
10numbers = [10, 20, 30]
11gen = safe_divide_generator(numbers, 0) # Create generator with divisor as 0.
12for value in gen:
13 print(value) # Output: inf, inf, inf
In questo esempio, viene effettuata la gestione corretta degli errori in caso di divisione per zero.
Traccia dello stack di un generatore
Se si verifica un'eccezione all'interno del generatore, verrà sollevata quando il generatore viene ripreso.
1def error_generator():
2 """A generator that yields values and raises an error."""
3 yield 1
4 raise ValueError("An error occurred") # Raise a ValueError intentionally.
5 yield 2
6
7gen = error_generator()
8print(next(gen)) # Output: 1 (first value yielded)
9try:
10 print(next(gen)) # Attempt to get the next value, which raises an error
11except ValueError as e:
12 print(e) # Output: An error occurred (exception message is printed)
- Questo generatore restituisce prima
1
. L'errore sollevato durante la ripresa viene intercettato e mostrato come messaggio di errore.
Esempi di utilizzo dei generatori
Lettura di un file riga per riga (adatto a file di grandi dimensioni)
1def read_large_file(filepath):
2 with open(filepath, 'r') as f:
3 for line in f:
4 yield line.strip()
- Questa funzione legge un file di testo riga per riga usando un iteratore, rimuove gli spazi bianchi da ogni riga e la restituisce come generatore, permettendo di elaborare file di grandi dimensioni con un uso ridotto di memoria.
Generatore per la sequenza di Fibonacci
1def fibonacci(limit):
2 a, b = 0, 1
3 while a < limit:
4 yield a
5 a, b = b, a + b
6
7for n in fibonacci(100):
8 print(n)
- Questo codice utilizza un generatore per generare in sequenza i numeri di Fibonacci inferiori al limite massimo e li visualizza usando un ciclo
for
.
Casi d'uso
I generatori possono anche essere utilizzati nei seguenti scenari.
- Elaborazione sequenziale di grandi file CSV o di log
- Paginazione API
- Elaborazione di dati in streaming (ad esempio, Kafka, dispositivi IoT)
Riepilogo
Concetto | Punto chiave |
---|---|
yield |
Mette in pausa e restituisce un valore |
Funzione generatrice | Una funzione che contiene yield e restituisce un iteratore quando viene chiamata |
Vantaggi | Efficiente in termini di memoria e ideale per l'elaborazione di grandi insiemi di dati |
Espressione generatrice | Permette una sintassi concisa come (x for x in iterable) |
Utilizzando i generatori, puoi elaborare grandi set di dati in modo efficiente risparmiando memoria e mantenendo il codice conciso.
Puoi seguire l'articolo sopra utilizzando Visual Studio Code sul nostro canale YouTube. Controlla anche il nostro canale YouTube.