Tracce
/
Python
Python
/
Programma
/
Generatori
Ge

Generatori in Python

1 esercizio

Informazioni su Generatori

Un generator è una funzione o un'espressione che restituisce un tipo speciale di iteratore, chiamato iteratore di generatori. Generator-iterators sono pigri: non memorizzano i loro values in memoria, ma generano i loro valori quando servono.

Una funzione generatore è simile a qualsiasi altra funzione, ma contiene una o più espressioni yield. Ogni yield sospende l'esecuzione del codice, salvando lo stato di esecuzione corrente (comprese tutte le variabili locali e le istruzioni try). Quando il generatore riprende, recupera lo stato dal punto in cui era stato sospeso, a differenza delle funzioni normali, che si azzerano a ogni chiamata.

Costruire un generatore

I generatori si costruiscono più o meno come le altre funzioni iterative o ricorsive, ma richiedono un'espressione yield, che esploreremo in dettaglio tra poco.

Un esempio è una funzione che restituisce i quadrati a partire da un array di numeri.
Così com'è scritta, deve elaborare tutto l'input prima di poter restituire qualsiasi valore:

>>> def squares(list_of_numbers):
...     squares = []
...     for number in list_of_numbers:
...         squares.append(number ** 2)
...     return squares

Puoi convertire quella funzione in un generatore così:

>>> def squares_generator(list_of_numbers):
...     for number in list_of_numbers:
...         yield number ** 2

Il motivo è che usi un generatore quando non ti servono tutti i valori in una volta sola.

Così risparmi memoria e potenza di calcolo, perché viene calcolato solo il valore su cui stai lavorando in quel momento.

Usare un generatore

I generatori (tecnicamente generator-iterator, vedi la nota qui sotto) sono un tipo di iterator e si possono usare ovunque in Python ci si aspetti un iterator o un iterable. Questo vale anche per funzioni o oggetti che richiedono un iterable/iterator come argomento. Per approfondire, vedi How to Make an Iterator in Python.

Note

Gli iteratori di generatori sono un sottoinsieme speciale degli iteratori. Gli Iterators sono il meccanismo o protocollo che consente di iterare sugli iterabili. Gli iteratori di generatori e gli iteratori restituiti dai comuni iterables di Python si comportano in modo molto simile, ma ci sono alcune differenze importanti da notare:

  • Sono valutati in modo pigro; l'iterazione è a senso unico e non si può «tornare indietro» a un valore precedente.
  • Vengono consumati quando si itera sui valori restituiti; non c'è alcun ripristino né salvataggio in memoria.
  • Non si possono ordinare né invertire.
  • Non sono tipi sequenza e non hanno indexes. Non puoi fare riferimento a un valore precedente o futuro usando somme o sottrazioni e non puoi usare la notazione con parentesi quadre ([]) né lo slicing.
  • Non si possono usare con la funzione len(), perché non hanno una lunghezza.
  • Possono essere finiti o infiniti: fai attenzione quando raccogli tutti i valori da un generator-iterator infinito!

Per usare il generatore squares_generator():

>>> squared_numbers = squares_generator([1, 2, 3, 4])

>>> for square in squared_numbers:
...     print(square)
...
1
4
9
16

I valori all'interno di un generatore si possono anche produrre o recuperare tramite la funzione next(). next() chiama il metodo __next__() di un oggetto generatore, «avanzando» o valutando il codice del generatore fino alla sua espressione yield, che a quel punto «produce» o restituisce il valore.

>>> squared_numbers = squares_generator([1, 2])

>>> next(squared_numbers)
1
>>> next(squared_numbers)
4

Quando un generator è completamente consumato e non ha più valori da restituire, solleva un errore StopIteration.

>>> next(squared_numbers)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Differenza tra iterabili e generatori

I generatori sono un sottoinsieme speciale degli iteratori. Gli Iterators sono il meccanismo o protocollo che consente di iterare sugli iterabili. I generatori e gli iteratori restituiti dai comuni iterables di Python si comportano in modo molto simile, ma ci sono alcune differenze importanti da notare:

  • I generatori sono a senso unico; non si può «tornare indietro» a un valore precedente.

  • Iterare sui generatori consuma i valori restituiti; nessun ripristino.

  • I generatori (poiché sono valutati in modo pigro) non si possono ordinare né invertire.

  • I generatori non hanno indexes, quindi non puoi fare riferimento a un valore precedente o futuro usando somme o sottrazioni.

  • I generatori non si possono usare con la funzione len().

  • I generatori possono essere finiti o infiniti: fai attenzione quando raccogli tutti i valori da un generatore infinito.

L'espressione yield

L'espressione yield è molto simile all'espressione return.

A differenza dell'espressione return, yield cede i valori al chiamante in un punto specifico, sospendendo la valutazione o la restituzione di ulteriori valori finché non vengono richiesti.

Quando viene valutato yield, l'esecuzione della funzione che lo contiene si mette in pausa e vengono restituiti i valori della funzione in quel preciso momento.

La funzione poi resta nello scope, e quando viene chiamato __next__(), l'esecuzione riprende finché non si incontra di nuovo yield.

Note

Usare espressioni yield è vietato al di fuori delle funzioni.

>>> def infinite_sequence():
...     current_number = 0
...     while True:
...         yield current_number
...         current_number += 1

>>> lets_try = infinite_sequence()
>>> lets_try.__next__()
0
>>> lets_try.__next__()
1

Perché usare i generatori?

I generatori sono utili in moltissime applicazioni.

Quando lavori con una raccolta di grandi dimensioni, potresti non voler mettere tutti i suoi valori in memory. Un generatore può servire a elaborare dati più grandi un pezzo alla volta, risparmiando memoria e migliorando le prestazioni.

I generatori sono anche molto utili quando un processo o un calcolo è complesso, costoso o infinito:

>>> def infinite_sequence():
...     current_number = 0
...     while True:
...         yield current_number
...         current_number += 1

Ora, ogni volta che si chiama __next__() sull'oggetto infinite_sequence, verrà restituito il numero precedente + 1.

Modifica tramite GitHub Il collegamento si apre in una nuova finestra o scheda

Impara Generatori

La pratica è bloccata

Sblocca 2 altri esercizi per esercitarti su Generatori