I ge­ne­ra­to­ri Python sono un tipo par­ti­co­la­re di funzioni in Python. Generano valori in modo graduale, per­met­ten­do di lavorare riducendo il consumo di memoria.

Cosa sono esat­ta­men­te i ge­ne­ra­to­ri Python?

I ge­ne­ra­to­ri Python sono funzioni speciali che re­sti­tui­sco­no un iteratore Python. La creazione dei ge­ne­ra­to­ri Python è simile a una normale de­fi­ni­zio­ne di una funzione. La dif­fe­ren­za è nel dettaglio: anziché un’istru­zio­ne return, per i ge­ne­ra­to­ri è prevista un’istru­zio­ne yield. Inoltre, le funzioni di ge­ne­ra­to­re come gli iteratori im­ple­men­ta­no una funzione next().

N.B.

I ge­ne­ra­to­ri Python sono concetti avanzati della pro­gram­ma­zio­ne in Python. Se il vostro livello di co­no­scen­za è superiore a quanto riportato nei tutorial di Python per i prin­ci­pian­ti, vi con­si­glia­mo gli articoli seguenti:

La parola chiave yield

Se avete già espe­rien­za con altri linguaggi di pro­gram­ma­zio­ne o con Python, co­no­sce­re­te già l’istru­zio­ne return, che è usata per passare i valori calcolati dalle funzioni all’istanza chiamante nel codice di programma. Una volta raggiunta l’istru­zio­ne return di una funzione, la funzione viene chiusa, ter­mi­nan­do­ne l’ese­cu­zio­ne. All’oc­cor­ren­za basta chiamarla nuo­va­men­te.

Con yield, il com­por­ta­men­to è diverso: la parola chiave so­sti­tui­sce l’istru­zio­ne return nei ge­ne­ra­to­ri Python. Se ora chiamate un ge­ne­ra­to­re, viene re­sti­tui­to il valore passato all’istru­zio­ne yield. Dopodiché, il ge­ne­ra­to­re Python non viene tuttavia chiuso, ma soltanto in­ter­rot­to. Lo stato corrente della funzione di ge­ne­ra­to­re viene “salvato”. A una nuova chiamata di funzione del ge­ne­ra­to­re si salta quindi al punto salvato.

Campi di ap­pli­ca­zio­ne dei ge­ne­ra­to­ri Python

Siccome i ge­ne­ra­to­ri Python procedono secondo il principio della “va­lu­ta­zio­ne pigra”, valutando i valori soltanto quando servono davvero, le funzioni di ge­ne­ra­to­re sono perfette per lavorare con quantità di dati molto grandi.

Una funzione normale per prima cosa ca­ri­che­reb­be 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 suf­fi­cien­te, generando un errore di memoria. I ge­ne­ra­to­ri per­met­to­no di aggirare questi problemi fa­cil­men­te leggendo il file riga per riga. La parola chiave yield re­sti­tui­sce il valore ne­ces­sa­rio al momento, poi in­ter­rom­pe l’ese­cu­zio­ne della funzione fino alla chiamata suc­ces­si­va, che elabora un’altra riga del file.

Consiglio

Molte ap­pli­ca­zio­ni web ri­chie­do­no l’ela­bo­ra­zio­ne di grandi quantità di dati. Python è adatto anche per i progetti web. Con Deploy Now potete creare i vostri progetti web più ve­lo­ce­men­te sfrut­tan­do la com­pi­la­zio­ne e la di­stri­bu­zio­ne au­to­ma­ti­ca at­tra­ver­so GitHub.

Quindi, i ge­ne­ra­to­ri Python fa­ci­li­ta­no enor­me­men­te non soltanto la gestione di grandi quantità di dati, ma anche il lavoro con l’infinito. Poiché la memoria locale non è infinita, i ge­ne­ra­to­ri sono l’unica pos­si­bi­li­tà per generare liste infinite (o simili) in Python.

Leggere i file CSV con i ge­ne­ra­to­ri Python

Come già men­zio­na­to, i ge­ne­ra­to­ri sono adatti so­prat­tut­to 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 im­por­tia­mo prima il modulo csv per avere accesso alle funzioni Python per elaborare i file CSV. Vedete poi la de­fi­ni­zio­ne di un ge­ne­ra­to­re Python chiamato “leggi_csv” che, come le de­fi­ni­zio­ni 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 re­sti­tui­ta con la parola chiave “yield”.

Al di fuori della funzione di ge­ne­ra­to­re, le righe re­sti­tui­te dal ge­ne­ra­to­re 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 ge­ne­ra­to­ri Python

Lo­gi­ca­men­te non è possibile salvare una struttura di dati infinita in locale sul computer. Tuttavia, queste strutture sono es­sen­zia­li per alcune ap­pli­ca­zio­ni. Anche qui le funzioni di ge­ne­ra­to­re aiutano a elaborare tutti gli elementi in suc­ces­sio­ne, 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 ge­ne­ra­to­re Python de­no­mi­na­to “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 re­sti­tui­to il valore attuale della variabile e l’ese­cu­zio­ne della funzione di ge­ne­ra­to­re viene in­ter­rot­ta. Se la funzione viene chiamata un’altra volta, il numero pre­ce­den­te­men­te re­sti­tui­to viene in­cre­men­ta­to di 1 e il ge­ne­ra­to­re gira di nuovo finché l’in­ter­pre­te non trova la parola chiave “yield”. Nel ciclo for sotto la funzione di ge­ne­ra­to­re sono re­sti­tui­ti i numeri prodotti dal ge­ne­ra­to­re. Se non viene in­ter­rot­to ma­nual­men­te, il programma gira all’infinito.

Notazione breve dei ge­ne­ra­to­ri Python

Con le com­pren­sio­ni di lista potete creare liste in Python in una sola riga di codice. Una notazione breve simile è di­spo­ni­bi­le anche per i ge­ne­ra­to­ri. Os­ser­via­mo un ge­ne­ra­to­re che in­cre­men­ta del valore 1 i numeri da 0 a 9. Somiglia al ge­ne­ra­to­re 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 ge­ne­ra­to­re in una riga di codice, uti­liz­za­te un’istru­zio­ne for fra parentesi tonde come nell’esempio seguente:

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

Se ora re­sti­tui­te questo ge­ne­ra­to­re, ottenete l’output seguente:

<generator object <genexpr> at 0x0000020CC5A2D6C8>

Vedete dunque dove si trova l’oggetto ge­ne­ra­to­re creato nella vostra memoria. Per accedere all’output del ge­ne­ra­to­re è di­spo­ni­bi­le 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 in­cre­men­ta­ti di 1:

1
2
3

Confronto tra ge­ne­ra­to­ri e com­pren­sio­ni di lista

La notazione breve dei ge­ne­ra­to­ri ricorda for­te­men­te quella delle com­pren­sio­ni di lista. L’unica dif­fe­ren­za visiva è nelle parentesi: mentre per le com­pren­sio­ni si usano le parentesi quadre, per creare ge­ne­ra­to­ri Python servono le parentesi tonde. Ma a livello interno c’è una dif­fe­ren­za molto più so­stan­zia­le: il fab­bi­so­gno di memoria dei ge­ne­ra­to­ri è net­ta­men­te 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 re­sti­tui­sce il fab­bi­so­gno di memoria della lista e del ge­ne­ra­to­re equi­va­len­te:

912
120

Mentre la lista necessita di uno spazio di memoria di 912 byte, al ge­ne­ra­to­re ne bastano 120. Questa dif­fe­ren­za risulta ancora più grande con l’aumentare della quantità di dati da elaborare.

Vai al menu prin­ci­pa­le