I generatori Python, per programmare ottimizzando la memoria

I generatori Python sono un tipo particolare di funzioni in Python. Generano valori in modo graduale, permettendo di lavorare riducendo il consumo di memoria.

Cosa sono esattamente i generatori Python?

I generatori Python sono funzioni speciali che restituiscono un iteratore Python. La creazione dei generatori Python è simile a una normale definizione di una funzione. La differenza è nel dettaglio: anziché un’istruzione return, per i generatori è prevista un’istruzione yield. Inoltre, le funzioni di generatore come gli iteratori implementano una funzione next().

N.B.

I generatori Python sono concetti avanzati della programmazione in Python. Se il vostro livello di conoscenza è superiore a quanto riportato nei tutorial di Python per i principianti, vi consigliamo gli articoli seguenti:

La parola chiave yield

Se avete già esperienza con altri linguaggi di programmazione o con Python, conoscerete già l’istruzione return, che è usata per passare i valori calcolati dalle funzioni all’istanza chiamante nel codice di programma. Una volta raggiunta l’istruzione return di una funzione, la funzione viene chiusa, terminandone l’esecuzione. All’occorrenza basta chiamarla nuovamente.

Con yield, il comportamento è diverso: la parola chiave sostituisce l’istruzione return nei generatori Python. Se ora chiamate un generatore, viene restituito il valore passato all’istruzione yield. Dopodiché, il generatore Python non viene tuttavia chiuso, ma soltanto interrotto. Lo stato corrente della funzione di generatore viene “salvato”. A una nuova chiamata di funzione del generatore si salta quindi al punto salvato.

Campi di applicazione dei generatori Python

Siccome i generatori Python procedono secondo il principio della “valutazione pigra”, valutando i valori soltanto quando servono davvero, le funzioni di generatore sono perfette per lavorare con quantità di dati molto grandi.

Una funzione normale per prima cosa caricherebbe l’intero contenuto del file in una variabile e quindi nella memoria. In presenza di grandi quantità di dati la RAM locale potrebbe non essere sufficiente, generando un errore di memoria. I generatori permettono di aggirare questi problemi facilmente leggendo il file riga per riga. La parola chiave yield restituisce il valore necessario al momento, poi interrompe l’esecuzione della funzione fino alla chiamata successiva, che elabora un’altra riga del file.

Consiglio

Molte applicazioni web richiedono l’elaborazione di grandi quantità di dati. Python è adatto anche per i progetti web. Con Deploy Now potete creare i vostri progetti web più velocemente sfruttando la compilazione e la distribuzione automatica attraverso GitHub.

Quindi, i generatori Python facilitano enormemente non soltanto la gestione di grandi quantità di dati, ma anche il lavoro con l’infinito. Poiché la memoria locale non è infinita, i generatori sono l’unica possibilità per generare liste infinite (o simili) in Python.

Leggere i file CSV con i generatori Python

Come già menzionato, i generatori sono adatti soprattutto per lavorare con grandi quantità di dati. Il programma seguente permette di leggere un file CSV riga per riga, riducendo il consumo di memoria:

import csv
def leggi_csv(nomefile):
 with open(nomefile, 'r') come file:
  tmp = csv.reader(file)
  for riga in tmp:
   yield riga
for riga in leggi_csv('test.csv'):
 print(riga)
Python

Nell’esempio di codice importiamo prima il modulo csv per avere accesso alle funzioni Python per elaborare i file CSV. Vedete poi la definizione di un generatore Python chiamato “leggi_csv” che, come le definizioni di funzioni, inizia con la parola chiave “def”. Dopo che il file è stato aperto, nel ciclo for in Python si itera sul file riga per riga. Ogni riga viene restituita con la parola chiave “yield”.

Al di fuori della funzione di generatore, le righe restituite dal generatore Python vengono riportate sulla console l’una dopo l’altra. A questo scopo si usa la funzione print in Python.

Creare strutture di dati infinite con i generatori Python

Logicamente non è possibile salvare una struttura di dati infinita in locale sul computer. Tuttavia, queste strutture sono essenziali per alcune applicazioni. Anche qui le funzioni di generatore aiutano a elaborare tutti gli elementi in successione, senza inondare la memoria. Un esempio di sequenza infinita di numeri naturali potrebbe apparire come segue nel codice Python:

def numeri_naturali():
 n = 0
 while True:
  yield n
  n += 1
for numero in numeri_naturali():
 print(numero)
Python

Per prima cosa si definisce un generatore Python denominato “numeri_naturali” che determina il valore di inizio della variabile “n”. Poi si avvia un ciclo while in Python che gira all’infinito. Con “yield” viene restituito il valore attuale della variabile e l’esecuzione della funzione di generatore viene interrotta. Se la funzione viene chiamata un’altra volta, il numero precedentemente restituito viene incrementato di 1 e il generatore gira di nuovo finché l’interprete non trova la parola chiave “yield”. Nel ciclo for sotto la funzione di generatore sono restituiti i numeri prodotti dal generatore. Se non viene interrotto manualmente, il programma gira all’infinito.

Notazione breve dei generatori Python

Con le comprensioni di lista potete creare liste in Python in una sola riga di codice. Una notazione breve simile è disponibile anche per i generatori. Osserviamo un generatore che incrementa del valore 1 i numeri da 0 a 9. Somiglia al generatore che abbiamo usato per generare la sequenza infinita di numeri naturali.

def numeri_naturali():
 n = 0
 while n <= 9:
  yield n
  n+=1
Python

Se volete scrivere questo generatore in una riga di codice, utilizzate un’istruzione for fra parentesi tonde come nell’esempio seguente:

increment_generator = (n + 1 for n in range(10))
Python

Se ora restituite questo generatore, ottenete l’output seguente:

<generator object <genexpr> at 0x0000020CC5A2D6C8> 

Vedete dunque dove si trova l’oggetto generatore creato nella vostra memoria. Per accedere all’output del generatore è disponibile la funzione next():

print(next(increment_generator))
print(next(increment_generator))
print(next(increment_generator))
Python

Questa sezione di codice fornisce l’output seguente, in cui i numeri da 0 a 2 sono stati incrementati di 1:

1
2
3

Confronto tra generatori e comprensioni di lista

La notazione breve dei generatori ricorda fortemente quella delle comprensioni di lista. L’unica differenza visiva è nelle parentesi: mentre per le comprensioni si usano le parentesi quadre, per creare generatori Python servono le parentesi tonde. Ma a livello interno c’è una differenza molto più sostanziale: il fabbisogno di memoria dei generatori è nettamente inferiore a quello delle liste.

import sys
increment_list = [n + 1 for n in range(100)]
increment_generator = (n + 1 for n in range(100))
print(sys.getsizeof(increment_list))
print(sys.getsizeof(increment_generator))
Python

Il programma sopra restituisce il fabbisogno di memoria della lista e del generatore equivalente:

912
120

Mentre la lista necessita di uno spazio di memoria di 912 byte, al generatore ne bastano 120. Questa differenza risulta ancora più grande con l’aumentare della quantità di dati da elaborare.

Per offrirti una migliore esperienza di navigazione online questo sito web usa dei cookie, propri e di terze parti. Continuando a navigare sul sito acconsenti all’utilizzo dei cookie. Scopri di più sull’uso dei cookie e sulla possibilità di modificarne le impostazioni o negare il consenso.