lettura facile

I generatori in Python

Nel linguaggio Python un generatore è una funzione che produce una sequenza di risultati invece di un singolo valore, ogni volta che viene chiamata.

A differenza delle funzioni tradizionali che restituiscono un singolo valore e terminano, un generatore può produrre una serie di valori nel tempo. Questo comportamento è ottenuto utilizzando la parola chiave yield al posto di return. Questo permette a un generatore di fornire un valore al chiamante senza perdere lo stato in cui la funzione era, permettendo così alla funzione di continuare da dove era rimasta alla successiva chiamata.

Come funziona?

Ogni volta che un generatore chiama yield, la funzione emette un valore al codice chiamante e poi si "congela" e rimane in questo stato sospeso fino alla prossima volta che viene chiamato. 

Il namespace locale della funzione generatore viene salvato per poter essere riutilizzato alla chiamata successiva.

In questo modo, quando viene richiamato, il generatore riprende l'esecuzione immediatamente dopo il punto yield dove si era interrotto.

A cosa serve? Questa caratteristica rende i generatori particolarmente utili per lavorare con flussi di dati o sequenze di grandi dimensioni, perché occupa una minore quantità di memoria. Un generatore, quindi, è un modo efficiente per implementare degli iteratori. Ti permette di creare oggetti iterabili in modo efficiente.

Ecco un esempio pratico di generatore.

Questo codice definisce un generatore mio_generatore che produce tre valori: 1, 2 e 3.

def serie():
    yield 1
    yield 2
    yield 3

Quando la funzione generatore viene chiamata restituisce un oggetto generatore tramite il quale sono prodotti gli elementi della sequenza uno alla volta.

oggetto=serie()

Una volta ottenuto l'oggetto generatore, puoi ottenere i singoli elementi in sequenza tramite l'attributo __next__()

print(oggetto.__next__())

1

A ogni richiesta l'oggetto restituisce l'elemento corrispondente.

print(oggetto.__next__())

2

Dopo l'ultimo elemento della sequenza viene restituita un'eccezione StopIteration e la generazione si conclude.

print(oggetto.__next__())

3

In alternativa, puoi iterare automaticamente nel generatore usando un ciclo.

Quando un ciclo for chiama il generatore, la funzione esegue il blocco del codice fino al primo yield, produce il valore, e poi si mette in pausa.

for valore in serie():
    print(valore)

Alla successiva iterazione del ciclo, la funzione continua da dove era rimasta, fino al prossimo yield o fino a che non termina.

Complessivamente, il generatore viene chiamato tre volte e a ogni iterazione stampa il valore successivo della sequenza.

1
2
3

Le espressioni con i generatori

Oltre alle funzioni-generatore, Python supporta anche le espressioni-generatore, che sono una sintassi compatta per costruire generatori.

Le espressioni generatore sono simili alle list comprehensions, ma invece di costruire una lista, costruiscono un generatore che produce elementi su richiesta.

Ecco un esempio pratico.

Le espressioni generatore sono racchiuse tra parentesi tonde:

gen_expr = (x ** 2 for x in range(10))
for valore in gen_expr:
    print(valore)

Questa espressione generatore produce i quadrati di numeri da 0 a 9, uno alla volta.

0
1
4
9
16
25
36
49
64
81

Per selezionare solo i numeri pari da 0 a 9 puoi aggiungere una condizione nell'espressione.

gen_expr = (x ** 2 for x in range(10) if x%2==0)
for valore in gen_expr:
    print(valore)

0
4
16
36
64

Quando i generatori sono utili?

I generatori sono efficienti in termini di memoria perché producono valori "al volo" senza memorizzare l'intera sequenza in memoria.

Quindi, sono molto utili se devi lavorare con sequenze di dati di grandi dimensioni, come file di log o grandi set di dati, in cui memorizzare tutti i dati in memoria sarebbe impraticabile o inefficiente.

Inoltre, i generatori implementano la valutazione pigra ( lazy evaluation ), il che significa che producono valori solo quando sono richiesti.

Ad esempio, quando crei una lista con i quadrati dei primi 10 numeri, Python genera tutti i numeri e li salva in una lista.

squares = [x**2 for x in range(10)]
print(squares)

[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Puoi ottenere lo stesso risultato con un generatore che, invece, genera un quadrato alla volta occupando meno memoria

gen_expr = (x ** 2 for x in range(10))
for valore in gen_expr:
    print(valore)

0
1
4
9
16
25
36
49
64
81

Quando devi generare decine di migliaia di valori, il vantaggio dei generatori diventa evidente.

 




Se qualcosa non ti è chiaro, scrivi la tua domanda nei commenti.




FacebookTwitterLinkedinLinkedin