Parola chiave yield in Python: cos’è e come usarla
La parola chiave yield in Python trasforma una normale funzione in un generatore, che produce una sequenza di valori su richiesta invece di calcolarli tutti in una volta.
Le funzioni Python non hanno sempre un'istruzione return. Le funzioni generatrici sono funzioni che hanno la parola chiave yield invece di return.
Queste funzioni producono iteratori generatori, cioè oggetti che rappresentano un flusso di dati. Gli elementi rappresentati da un iteratore vengono creati e restituiti solo quando richiesti. Questo tipo di valutazione è spesso chiamato valutazione pigra (lazy evaluation).
Quando si lavora con dataset di grandi dimensioni, i generatori offrono un'alternativa efficiente in memoria rispetto all'archiviazione dei dati in liste, tuple e altre strutture dati che richiedono spazio in memoria per ciascun elemento. Le funzioni generatrici possono anche creare iteratori infiniti, cosa non possibile con strutture valutate avidamente come liste e tuple.
Prima di iniziare, ricapitoliamo le differenze tra funzioni e generatori:
Caratteristica
Funzione
Generatore
Produzione dei valori
Restituisce tutti i valori in una volta
Restituisce i valori uno alla volta, su richiesta
Esecuzione
Esegue completamente prima di restituire
Si mette in pausa dopo ogni yield, riprende quando è richiesto il valore successivo
Parola chiave
return
yield
Uso della memoria
Potenzialmente elevato, memorizza l'intera sequenza in memoria
Basso, memorizza solo il valore corrente e lo stato per il successivo
Iterazione
Possibili iterazioni multiple, ma richiede di memorizzare l'intera sequenza
Progettato per un'unica passata, più efficiente per sequenze grandi o infinite
Usare yield di Python per creare funzioni generatrici
Il termine generatore in Python può riferirsi a un iteratore generatore o a una funzione generatrice. Sono oggetti diversi ma correlati in Python. In questo tutorial, per evitare confusione, si usano spesso i termini per esteso.
Partiamo dalle funzioni generatrici. Una funzione generatrice assomiglia a una normale funzione ma contiene la parola chiave yield invece di return.
Quando un programma Python chiama una funzione generatrice, crea un iteratore generatore. Gli iteratori restituiscono un valore su richiesta e mettono in pausa l'esecuzione finché non serve un altro valore. Vediamo un esempio per spiegare questo concetto e mostrare la differenza tra funzioni normali e funzioni generatrici.
Usare una funzione normale
Per prima cosa, definiamo una funzione normale, che contiene un'istruzione return. Questa funzione accetta una sequenza di parole e una lettera, e restituisce una lista con il numero di occorrenze della lettera in ciascuna parola:
deffind_letter_occurrences(words, letter):
output =[]for word in words:
output.append(word.count(letter))return output
print(
find_letter_occurrences(["apple","banana","cherry"],"a"))Offerto da
La funzione restituisce una lista contenente 1, 3 e 0, dato che c'è una a in apple, tre occorrenze di a in banana e nessuna in cherry. La stessa funzione può essere riscritta usando le list comprehension invece di inizializzare una lista vuota e usare.append():
deffind_letter_occurrences(words, letter):return[word.count(letter)for word in words]Offerto da
Questa funzione normale restituisce una lista con tutti i risultati ogni volta che viene chiamata. Tuttavia, se la lista di parole è grande, chiamare questa funzione normale richiede molta memoria, poiché il programma crea e memorizza una nuova lista della stessa dimensione dell'originale. Se questa funzione viene usata ripetutamente su diversi argomenti di input, o funzioni simili eseguono altre operazioni sui dati originali, la pressione sulla memoria può aumentare rapidamente.
Usare una funzione generatrice
Al suo posto si può usare una funzione generatrice:
deffind_letter_occurrences(words, letter):for word in words:yield word.count(letter)
words =["apple","banana","cherry"]
letter ="a"
output = find_letter_occurrences(words, letter)print(output)Offerto da
<generator object find_letter_occurrences at 0x102935e00>Offerto da
La funzione include la parola chiave yield invece di return. Questa funzione generatrice restituisce un oggetto generatore quando viene chiamata, assegnato a output. Questo oggetto è un iteratore. Non contiene i dati con il numero di occorrenze della lettera in ciascuna parola. Il generatore, invece, creerà e produrrà i valori quando servono. Recuperiamo il primo valore da questo iteratore generatore:
La funzione built-in next() è un modo per ottenere il valore successivo da un iteratore. Più avanti in questo tutorial vedremo altri modi.
Il codice nella funzione generatrice viene eseguito fino a quando il programma raggiunge la riga con la parola chiave yield. In questo esempio, il ciclo for inizia la prima iterazione e preleva il primo elemento nella lista words. Il metodo di stringa .count() restituisce un intero, che in questo caso è 1, poiché c'è un'occorrenza di a in apple. Il generatore produce questo valore, che viene restituito da next(output).
Il generatore output mette in pausa l'esecuzione a questo punto. Quindi il generatore ha completato la prima iterazione del ciclo for e ha trovato il numero di occorrenze della lettera a nella prima parola dell'elenco. Ora attende finché non serve di nuovo.
Se la funzione built-in next() viene chiamata di nuovo con output come argomento, il generatore riprenderà l'esecuzione dal punto in cui era in pausa:
Il generatore riparte dalla riga con yield nella prima iterazione del ciclo for. Poiché il ciclo for non ha altre righe di codice, torna all'inizio del ciclo e preleva il secondo elemento dalla lista words. Il valore restituito da .count() è 3 in questo caso, e questo valore viene prodotto. Il generatore si mette di nuovo in pausa a questo punto dell'esecuzione.
La terza chiamata a next() riprende questa esecuzione:
La seconda iterazione raggiunge la fine del ciclo for, che passa alla terza iterazione. Il codice avanza di nuovo fino alla riga con yield, questa volta producendo l'intero 0 poiché non ci sono occorrenze di a in cherry.
Il generatore si mette di nuovo in pausa. Il programma determina il destino del generatore solo quando chiamiamo next() una quarta volta:
Traceback (most recent call last):
...
StopIterationOfferto da
L'esecuzione riprende dalla fine della terza iterazione del ciclo for. Tuttavia, il ciclo ha raggiunto la fine poiché non ci sono più elementi nella lista words. Il generatore solleva un'eccezione StopIteration.
Nella maggior parte dei casi d'uso, gli elementi del generatore non vengono accessi direttamente con next() ma tramite un altro processo di iterazione. L'eccezione StopIteration segnala la fine del processo di iterazione. Approfondiremo questo aspetto nella sezione seguente del tutorial.
Python offre un altro modo per creare iteratori generatori quando la loro operazione può essere rappresentata da una singola espressione, come nell'esempio precedente. L'iteratore generatore output può essere creato usando un espressione generatrice:
words =["apple","banana","cherry"]
letter ="a"
output =(word.count(letter)for word in words)print(next(output))print(next(output))print(next(output))print(next(output))Offerto da
L'espressione tra parentesi assegnata a output è un'espressione generatrice, che crea un iteratore generatore simile a quello prodotto dalla funzione generatrice find_letter_occurrences().
Concludiamo questa sezione con un altro esempio di funzione generatrice per evidenziare come l'esecuzione si metta in pausa e riprenda ogni volta che serve un elemento:
defshow_status():print("Start")yieldprint("Middle")yieldprint("End")yield
status = show_status()next(status)Offerto da
Questa funzione generatrice non ha un ciclo. Contiene invece tre righe con la parola chiave yield. Il codice crea un iteratore generatore status quando chiama la funzione generatrice show_status(). La prima volta che il programma chiama next(status), il generatore avvia l'esecuzione. Stampa la stringa "Start" e si mette in pausa dopo la prima espressione yield. Il generatore produce None poiché non c'è alcun oggetto dopo la parola chiave yield.
La stringa "Middle" viene stampata solo quando next() viene chiamato una seconda volta:
Il generatore si mette in pausa sull'ultima espressione yield. Solleverà un'eccezione StopIteration alla successiva richiesta di un valore da questo iteratore generatore:
Traceback (most recent call last):
...
StopIterationOfferto da
Esploreremo altri modi di usare i generatori nella sezione seguente.
Lavorare con gli iteratori generatori
Le funzioni generatrici creano iteratori generatori, e gli iteratori sono iterabili. Scomponiamo questa frase. Ogni volta che il programma chiama una funzione generatrice, crea un iteratore. Poiché gli iteratori sono iterabili, possono essere usati nei cicli for e in altri processi iterativi.
Quindi, la funzione built-in next() non è l'unico modo per accedere agli elementi di un iteratore. In questa sezione esploriamo altri modi di lavorare con i generatori.
Usare il protocollo di iterazione di Python con gli iteratori generatori
Riprendiamo una funzione generatrice vista in una sezione precedente di questo tutorial:
deffind_letter_occurrences(words, letter):for word in words:yield word.count(letter)
words =["apple","banana","cherry"]
letter ="a"
output = find_letter_occurrences(words, letter)for value in output:print(value)Offerto da
Invece di usare next() più volte, questa versione del codice usa l'iteratore generatore output in un ciclo for. Poiché gli iteratori sono iterabili, possono essere usati nei cicli for. Il ciclo preleva elementi dall'iteratore generatore finché non ci sono più valori.
A differenza di strutture dati come liste e tuple, un iteratore può essere usato una sola volta. Il codice non ristampa i valori se proviamo a eseguire lo stesso ciclo for una seconda volta:
deffind_letter_occurrences(words, letter):for word in words:yield word.count(letter)
words =["apple","banana","cherry"]
letter ="a"
output = find_letter_occurrences(words, letter)print("First attempt:")for value in output:print(value)print("Second attempt:")for value in output:print(value)Offerto da
L'iteratore viene esaurito dal primo ciclo for, quindi non può più produrre valori. Se serve di nuovo il generatore dopo che è esaurito, dobbiamo creare un altro iteratore generatore dalla funzione generatrice.
È anche possibile avere più iteratori generatori contemporaneamente in un programma:
deffind_letter_occurrences(words, letter):for word in words:yield word.count(letter)
words =["apple","banana","cherry"]
letter ="a"
first_output = find_letter_occurrences(words, letter)
second_output = find_letter_occurrences(words, letter)print("First value of first_output:")print(next(first_output))print("Values of second_output:")for value in second_output:print(value)print("Remaining values of first_output:")for value in first_output:print(value)Offerto da
First value of first_output:
1
Values of second_output:
1
3
0
Remaining values of first_output:
3
0Offerto da
La funzione generatrice find_letter_occurrences() crea due iteratori generatori: first_output e second_output. Sebbene entrambi si riferiscano agli stessi dati nella lista words, avanzano in modo indipendente l'uno dall'altro.
In questo esempio si preleva il primo valore da first_output usando next(). L'iteratore generatore produce 1 e si mette in pausa. Il programma cicla poi su second_output. Poiché questo generatore non ha ancora prodotto alcun valore, il ciclo attraversa tutti i valori prodotti dal secondo iteratore. Infine, c'è un altro ciclo for che itera su first_output. Tuttavia, questo iteratore ha già prodotto il suo primo valore in precedenza. Il ciclo attraversa i valori rimanenti in questo iteratore.
Il ciclo for non è l'unico processo che può essere usato per iterare su iteratori generatori:
print(*find_letter_occurrences(words, letter))print(sorted(find_letter_occurrences(words, letter)))Offerto da
In questi esempi, il programma chiama direttamente la funzione generatrice per creare e usare l'iteratore generatore invece di assegnarlo a una variabile. Nel primo esempio, l'iteratore viene spacchettato usando l'asterisco. Questo processo si basa sullo stesso protocollo di iterazione del ciclo for.
Nel secondo esempio, l'iteratore generatore viene passato alla funzione built-in sorted(), che richiede un argomento iterabile. I generatori sono iterabili e, quindi, possono essere usati ogni volta che avviene un'iterazione in Python.
Creare iteratori infiniti
Un generatore produce un valore e si mette in pausa finché non è necessario il successivo. Ogni volta che il codice richiede un valore da un iteratore, il codice nella funzione generatrice verrà eseguito fino alla valutazione della successiva espressione yield. In tutti gli esempi visti finora, la funzione generatrice aveva un numero finito di espressioni yield. Tuttavia, è possibile creare un generatore che produca un numero infinito di valori usando un ciclo while nella funzione generatrice. Nel seguente esempio, il generatore restituisce un colore casuale dall'elenco di colori passato alla funzione generatrice:
import random
defget_color(colors):whileTrue:yield random.choice(colors)
output_colors = get_color(["red","green","blue"])print("First two colors:")print(next(output_colors))print(next(output_colors))print("Next 10 colors using a 'for' loop:")for _ inrange(10):print(next(output_colors))Offerto da
First two colors:
green
red
Next 10 colors using a 'for' loop:
blue
green
green
green
red
red
red
blue
green
redOfferto da
La funzione generatrice get_color() ha un'espressione yield all'interno di un ciclo while. Pertanto, il codice incontrerà sempre un'altra espressione yield mentre cerca il valore successivo. L'iteratore generatore output_colors produce un numero infinito di colori scelti casualmente dalla lista di input. Questo generatore non si esaurirà mai.
Non è possibile creare strutture dati infinite come liste e tuple. I generatori permettono a un programma di creare iterabili infiniti. Nota che se l'iteratore generatore viene usato direttamente in un ciclo for, il ciclo andrà avanti per sempre.
Concetti avanzati sui generatori
I generatori hanno casi d'uso più avanzati in Python. In questa sezione ne esploreremo alcuni.
Inviare un oggetto al generatore
I generatori possono anche accettare dati aggiuntivi da usare durante la valutazione del codice. L'istruzione che contiene la parola chiave yield è un'espressione che viene valutata a un valore. Questo valore può essere assegnato a una variabile all'interno della funzione generatrice. Iniziamo con un esempio di base per illustrare questo concetto:
defgenerator_function():
value =yield1print(f"The yield expression evaluates to: {value}")
value =yield2print(f"The yield expression evaluates to: {value}")
output = generator_function()print(next(output))print(next(output))print(next(output))Offerto da
1
The yield expression evaluates to: None
2
The yield expression evaluates to: None
Traceback (most recent call last):
...
StopIterationOfferto da
La parola chiave yield in Python crea un'espressione che viene valutata a un valore. Tuttavia, il valore di questa espressione all'interno della funzione generatrice non è lo stesso oggetto prodotto dal generatore. Considera la prima espressione yield. Il generatore produce l'intero 1. Quindi print(next(output)) visualizza 1 alla prima chiamata e mette in pausa l'esecuzione del generatore.
Tuttavia, l'espressione yield nella funzione generatrice si valuta in un oggetto, che il codice assegna al nome di variabile value. In questo esempio, yield assegna None a value. Questo processo si ripete per la seconda occorrenza di yield nella funzione generatrice. Lo scopo della terza chiamata a next() è assicurarsi che tutto il codice nella funzione generatrice venga eseguito.
Sostituiamo la seconda e la terza chiamata a next() con.send(), che è un metodo della classe generator:
defgenerator_function():
value =yield1print(f"The yield expression evaluates to: {value}")
value =yield2print(f"The yield expression evaluates to: {value}")
output = generator_function()print(next(output))print(output.send("Here's a value"))print(output.send("Here's another value"))Offerto da
1
The yield expression evaluates to: Here's a value
2
The yield expression evaluates to: Here's another value
Traceback (most recent call last):
...
StopIterationOfferto da
La funzione generatrice è invariata. Il generatore viene avviato chiamando next() e il codice gira finché non produce il primo intero, 1. Invece di usare next() la seconda volta, il programma chiama output.send(). Questo metodo invia un oggetto al generatore. In questo esempio, l'oggetto è una stringa. L'espressione yield nella funzione generatrice si valuta in questa stringa, che viene assegnata a value. Pertanto, il generatore può usare la stringa nel suo codice.
La seconda chiamata a .send() invia un nuovo oggetto al generatore, assegnato alla stessa variabile value. Dopo l'ultima chiamata a print() il generatore solleva StopIteration poiché non ci sono più espressioni yield.
Vediamo un altro esempio con .send(). Il seguente generatore visualizza il saldo di un conto, ma il saldo può essere aggiornato:
defget_balance(start_balance):
balance = start_balance
whileTrue:
amount =yield balance
if amount isnotNone:
balance += amount
current_balance = get_balance(100)print(next(current_balance))print(current_balance.send(10))print(current_balance.send(-20))print(next(current_balance))Offerto da
La funzione generatrice richiede un saldo iniziale quando viene chiamata. Il valore di balance può cambiare mentre il generatore esegue il codice. Qualsiasi oggetto inviato al generatore con .send() viene assegnato a amount. Questa variabile sarà None se il generatore produce un valore senza che gli venga inviato alcun oggetto, oppure conterrà l'oggetto inviato tramite .send().
L'iteratore generatore current_balance parte con un saldo di 100 $. Il generatore viene avviato chiamando next(), che fa partire l'esecuzione del codice fino alla produzione del primo valore.
Una volta avviato il generatore, è possibile far ripartire l'esecuzione usando .send() invece di next(). Il generatore aggiunge il valore inviato al saldo. Se non viene inviato alcun valore, come quando si chiama di nuovo next(), il generatore restituisce il saldo invariato.
Produrre direttamente da un altro iterabile
I generatori Python possono anche produrre valori direttamente da un altro generatore o iterabile usando la sintassi yield from. Vediamo un esempio di funzione generatrice che produce valori da una lista annidata:
defflatten(nested_list):for item in nested_list:ifisinstance(item,list):yieldfrom flatten(item)else:yield item
nested_list =[1,[2,3],[4,[5,6]],7]print(list(flatten(nested_list)))Offerto da
La funzione generatrice accetta una lista, che può includere al suo interno liste annidate. Il ciclo for itera sugli elementi della lista. Ogni elemento della lista esterna è o un valore, in questo caso un intero, o un'altra lista. Quando l'elemento non è una lista, il generatore lo produce.
Tuttavia, quando l'elemento è una lista, il generatore richiama ricorsivamente la funzione generatrice flatten() con la lista interna come argomento. Questo crea un altro iteratore generatore, che usa la lista interna come sorgente dati. Se questa riga usasse un'espressione yield, il primo generatore produrrebbe il secondo generatore. Invece, usando yield from, il primo generatore produce i valori del secondo generatore.
Riepilogo: yield vs. return
Le definizioni di funzione con return e yield sembrano simili, ma il loro comportamento è diverso. Riassumiamo le differenze chiave:
Funzione normale
Funzione generatrice
Parola chiave
return (implicita se non usata esplicitamente)
yield
Chiamata
Esegue il codice fino a incontrare return, poi restituisce il valore finale
Crea un iteratore generatore
Terminazione
Terminata dall'istruzione return
Messa in pausa da yield, può essere ripresa più tardi
Valore restituito
Singolo oggetto (può essere una struttura dati)
Iteratore generatore
Espressione yield
Non applicabile (crea un'istruzione)
Valuta a None o al valore inviato con .send()
Casi d'uso
Ideale per restituire un risultato finale
Ideale per creare un flusso di dati, specialmente sequenze grandi o infinite
Conclusione
La parola chiave yield di Python è usata nelle funzioni per definire una funzione generatrice. Quando chiamate, queste funzioni creano iteratori generatori. I generatori sono un esempio di valutazione pigra in Python, in cui le espressioni vengono valutate quando serve il valore invece che al momento dell'esecuzione dell'espressione. Pertanto, l'espressione yield è utile per creare un flusso di dati in cui i valori vengono generati su richiesta senza bisogno di memorizzarli in memoria.
Le considerazioni sull'efficienza sono importanti quando si lavora con grandi dataset che richiedono molte operazioni. Gli iteratori generatori di Python sono uno degli strumenti principali per manipolare grandi quantità di dati in modo efficiente.
Ho studiato Fisica e Matematica a livello UG presso l’Università di Malta. Poi mi sono trasferito a Londra e ho conseguito il dottorato in Fisica all’Imperial College. Ho lavorato su nuove tecniche ottiche per immagini della retina umana. Ora mi concentro sullo scrivere di Python, comunicare su Python e insegnare Python.
Scopri i 15 migliori server MCP remoti che stanno trasformando lo sviluppo AI nel 2026. Scopri come migliorano automazione, ragionamento, sicurezza e velocità dei workflow.
Impara Python per lo sviluppo software, dalla scrittura di funzioni alla definizione di classi. Acquisisci le competenze necessarie per dare il via alla tua carriera da sviluppatore!
Le funzioni Python non hanno sempre un'istruzione
return. Le funzioni generatrici sono funzioni che hanno la parola chiaveyieldinvece direturn.Queste funzioni producono iteratori generatori, cioè oggetti che rappresentano un flusso di dati. Gli elementi rappresentati da un iteratore vengono creati e restituiti solo quando richiesti. Questo tipo di valutazione è spesso chiamato valutazione pigra (lazy evaluation).
Quando si lavora con dataset di grandi dimensioni, i generatori offrono un'alternativa efficiente in memoria rispetto all'archiviazione dei dati in liste, tuple e altre strutture dati che richiedono spazio in memoria per ciascun elemento. Le funzioni generatrici possono anche creare iteratori infiniti, cosa non possibile con strutture valutate avidamente come liste e tuple.
Prima di iniziare, ricapitoliamo le differenze tra funzioni e generatori:
Caratteristica
Funzione
Generatore
Produzione dei valori
Restituisce tutti i valori in una volta
Restituisce i valori uno alla volta, su richiesta
Esecuzione
Esegue completamente prima di restituire
Si mette in pausa dopo ogni yield, riprende quando è richiesto il valore successivo
Parola chiave
return
yield
Uso della memoria
Potenzialmente elevato, memorizza l'intera sequenza in memoria
Basso, memorizza solo il valore corrente e lo stato per il successivo
Iterazione
Possibili iterazioni multiple, ma richiede di memorizzare l'intera sequenza
Progettato per un'unica passata, più efficiente per sequenze grandi o infinite
Usare
yielddi Python per creare funzioni generatriciIl termine generatore in Python può riferirsi a un iteratore generatore o a una funzione generatrice. Sono oggetti diversi ma correlati in Python. In questo tutorial, per evitare confusione, si usano spesso i termini per esteso.
Partiamo dalle funzioni generatrici. Una funzione generatrice assomiglia a una normale funzione ma contiene la parola chiave
yieldinvece direturn.Quando un programma Python chiama una funzione generatrice, crea un iteratore generatore. Gli iteratori restituiscono un valore su richiesta e mettono in pausa l'esecuzione finché non serve un altro valore. Vediamo un esempio per spiegare questo concetto e mostrare la differenza tra funzioni normali e funzioni generatrici.
Usare una funzione normale
Per prima cosa, definiamo una funzione normale, che contiene un'istruzione
return. Questa funzione accetta una sequenza di parole e una lettera, e restituisce una lista con il numero di occorrenze della lettera in ciascuna parola:La funzione restituisce una lista contenente 1, 3 e 0, dato che c'è una a in apple, tre occorrenze di a in banana e nessuna in cherry. La stessa funzione può essere riscritta usando le list comprehension invece di inizializzare una lista vuota e usare
.append():Questa funzione normale restituisce una lista con tutti i risultati ogni volta che viene chiamata. Tuttavia, se la lista di parole è grande, chiamare questa funzione normale richiede molta memoria, poiché il programma crea e memorizza una nuova lista della stessa dimensione dell'originale. Se questa funzione viene usata ripetutamente su diversi argomenti di input, o funzioni simili eseguono altre operazioni sui dati originali, la pressione sulla memoria può aumentare rapidamente.
Usare una funzione generatrice
Al suo posto si può usare una funzione generatrice:
La funzione include la parola chiave
yieldinvece direturn. Questa funzione generatrice restituisce un oggetto generatore quando viene chiamata, assegnato aoutput. Questo oggetto è un iteratore. Non contiene i dati con il numero di occorrenze della lettera in ciascuna parola. Il generatore, invece, creerà e produrrà i valori quando servono. Recuperiamo il primo valore da questo iteratore generatore:La funzione built-in
next()è un modo per ottenere il valore successivo da un iteratore. Più avanti in questo tutorial vedremo altri modi.Il codice nella funzione generatrice viene eseguito fino a quando il programma raggiunge la riga con la parola chiave
yield. In questo esempio, il cicloforinizia la prima iterazione e preleva il primo elemento nella listawords. Il metodo di stringa.count()restituisce un intero, che in questo caso è 1, poiché c'è un'occorrenza di a in apple. Il generatore produce questo valore, che viene restituito danext(output).Il generatore
outputmette in pausa l'esecuzione a questo punto. Quindi il generatore ha completato la prima iterazione del ciclofore ha trovato il numero di occorrenze della lettera a nella prima parola dell'elenco. Ora attende finché non serve di nuovo.Se la funzione built-in
next()viene chiamata di nuovo conoutputcome argomento, il generatore riprenderà l'esecuzione dal punto in cui era in pausa:Il generatore riparte dalla riga con
yieldnella prima iterazione del ciclofor. Poiché il ciclofornon ha altre righe di codice, torna all'inizio del ciclo e preleva il secondo elemento dalla listawords. Il valore restituito da.count()è 3 in questo caso, e questo valore viene prodotto. Il generatore si mette di nuovo in pausa a questo punto dell'esecuzione.La terza chiamata a
next()riprende questa esecuzione:La seconda iterazione raggiunge la fine del ciclo
for, che passa alla terza iterazione. Il codice avanza di nuovo fino alla riga conyield, questa volta producendo l'intero 0 poiché non ci sono occorrenze di a in cherry.Il generatore si mette di nuovo in pausa. Il programma determina il destino del generatore solo quando chiamiamo
next()una quarta volta:L'esecuzione riprende dalla fine della terza iterazione del ciclo
for. Tuttavia, il ciclo ha raggiunto la fine poiché non ci sono più elementi nella listawords. Il generatore solleva un'eccezioneStopIteration.Nella maggior parte dei casi d'uso, gli elementi del generatore non vengono accessi direttamente con
next()ma tramite un altro processo di iterazione. L'eccezioneStopIterationsegnala la fine del processo di iterazione. Approfondiremo questo aspetto nella sezione seguente del tutorial.Python offre un altro modo per creare iteratori generatori quando la loro operazione può essere rappresentata da una singola espressione, come nell'esempio precedente. L'iteratore generatore
outputpuò essere creato usando un espressione generatrice:L'espressione tra parentesi assegnata a
outputè un'espressione generatrice, che crea un iteratore generatore simile a quello prodotto dalla funzione generatricefind_letter_occurrences().Concludiamo questa sezione con un altro esempio di funzione generatrice per evidenziare come l'esecuzione si metta in pausa e riprenda ogni volta che serve un elemento:
Questa funzione generatrice non ha un ciclo. Contiene invece tre righe con la parola chiave
yield. Il codice crea un iteratore generatorestatusquando chiama la funzione generatriceshow_status(). La prima volta che il programma chiamanext(status), il generatore avvia l'esecuzione. Stampa la stringa"Start"e si mette in pausa dopo la prima espressioneyield. Il generatore produceNonepoiché non c'è alcun oggetto dopo la parola chiaveyield.La stringa
"Middle"viene stampata solo quandonext()viene chiamato una seconda volta:Il generatore si mette in pausa dopo la seconda espressione
yield. La terza chiamata anext()stampa la stringa finale,"End":Il generatore si mette in pausa sull'ultima espressione
yield. Solleverà un'eccezioneStopIterationalla successiva richiesta di un valore da questo iteratore generatore:Esploreremo altri modi di usare i generatori nella sezione seguente.
Lavorare con gli iteratori generatori
Le funzioni generatrici creano iteratori generatori, e gli iteratori sono iterabili. Scomponiamo questa frase. Ogni volta che il programma chiama una funzione generatrice, crea un iteratore. Poiché gli iteratori sono iterabili, possono essere usati nei cicli
fore in altri processi iterativi.Quindi, la funzione built-in
next()non è l'unico modo per accedere agli elementi di un iteratore. In questa sezione esploriamo altri modi di lavorare con i generatori.Usare il protocollo di iterazione di Python con gli iteratori generatori
Riprendiamo una funzione generatrice vista in una sezione precedente di questo tutorial:
Invece di usare
next()più volte, questa versione del codice usa l'iteratore generatoreoutputin un ciclofor. Poiché gli iteratori sono iterabili, possono essere usati nei ciclifor. Il ciclo preleva elementi dall'iteratore generatore finché non ci sono più valori.A differenza di strutture dati come liste e tuple, un iteratore può essere usato una sola volta. Il codice non ristampa i valori se proviamo a eseguire lo stesso ciclo
foruna seconda volta:L'iteratore viene esaurito dal primo ciclo
for, quindi non può più produrre valori. Se serve di nuovo il generatore dopo che è esaurito, dobbiamo creare un altro iteratore generatore dalla funzione generatrice.È anche possibile avere più iteratori generatori contemporaneamente in un programma:
La funzione generatrice
find_letter_occurrences()crea due iteratori generatori:first_outputesecond_output. Sebbene entrambi si riferiscano agli stessi dati nella listawords, avanzano in modo indipendente l'uno dall'altro.In questo esempio si preleva il primo valore da
first_outputusandonext(). L'iteratore generatore produce 1 e si mette in pausa. Il programma cicla poi susecond_output. Poiché questo generatore non ha ancora prodotto alcun valore, il ciclo attraversa tutti i valori prodotti dal secondo iteratore. Infine, c'è un altro cicloforche itera sufirst_output. Tuttavia, questo iteratore ha già prodotto il suo primo valore in precedenza. Il ciclo attraversa i valori rimanenti in questo iteratore.Il ciclo
fornon è l'unico processo che può essere usato per iterare su iteratori generatori:In questi esempi, il programma chiama direttamente la funzione generatrice per creare e usare l'iteratore generatore invece di assegnarlo a una variabile. Nel primo esempio, l'iteratore viene spacchettato usando l'asterisco. Questo processo si basa sullo stesso protocollo di iterazione del ciclo
for.Nel secondo esempio, l'iteratore generatore viene passato alla funzione built-in
sorted(), che richiede un argomento iterabile. I generatori sono iterabili e, quindi, possono essere usati ogni volta che avviene un'iterazione in Python.Creare iteratori infiniti
Un generatore produce un valore e si mette in pausa finché non è necessario il successivo. Ogni volta che il codice richiede un valore da un iteratore, il codice nella funzione generatrice verrà eseguito fino alla valutazione della successiva espressione
yield. In tutti gli esempi visti finora, la funzione generatrice aveva un numero finito di espressioniyield. Tuttavia, è possibile creare un generatore che produca un numero infinito di valori usando un ciclowhilenella funzione generatrice. Nel seguente esempio, il generatore restituisce un colore casuale dall'elenco di colori passato alla funzione generatrice:La funzione generatrice
get_color()ha un'espressioneyieldall'interno di un ciclowhile. Pertanto, il codice incontrerà sempre un'altra espressioneyieldmentre cerca il valore successivo. L'iteratore generatoreoutput_colorsproduce un numero infinito di colori scelti casualmente dalla lista di input. Questo generatore non si esaurirà mai.Non è possibile creare strutture dati infinite come liste e tuple. I generatori permettono a un programma di creare iterabili infiniti. Nota che se l'iteratore generatore viene usato direttamente in un ciclo
for, il ciclo andrà avanti per sempre.Concetti avanzati sui generatori
I generatori hanno casi d'uso più avanzati in Python. In questa sezione ne esploreremo alcuni.
Inviare un oggetto al generatore
I generatori possono anche accettare dati aggiuntivi da usare durante la valutazione del codice. L'istruzione che contiene la parola chiave
yieldè un'espressione che viene valutata a un valore. Questo valore può essere assegnato a una variabile all'interno della funzione generatrice. Iniziamo con un esempio di base per illustrare questo concetto:La parola chiave
yieldin Python crea un'espressione che viene valutata a un valore. Tuttavia, il valore di questa espressione all'interno della funzione generatrice non è lo stesso oggetto prodotto dal generatore. Considera la prima espressioneyield. Il generatore produce l'intero 1. Quindiprint(next(output))visualizza 1 alla prima chiamata e mette in pausa l'esecuzione del generatore.Tuttavia, l'espressione
yieldnella funzione generatrice si valuta in un oggetto, che il codice assegna al nome di variabilevalue. In questo esempio,yieldassegnaNoneavalue. Questo processo si ripete per la seconda occorrenza diyieldnella funzione generatrice. Lo scopo della terza chiamata anext()è assicurarsi che tutto il codice nella funzione generatrice venga eseguito.Sostituiamo la seconda e la terza chiamata a
next()con.send(), che è un metodo della classe generator:La funzione generatrice è invariata. Il generatore viene avviato chiamando
next()e il codice gira finché non produce il primo intero, 1. Invece di usarenext()la seconda volta, il programma chiamaoutput.send(). Questo metodo invia un oggetto al generatore. In questo esempio, l'oggetto è una stringa. L'espressioneyieldnella funzione generatrice si valuta in questa stringa, che viene assegnata avalue. Pertanto, il generatore può usare la stringa nel suo codice.La seconda chiamata a
.send()invia un nuovo oggetto al generatore, assegnato alla stessa variabilevalue. Dopo l'ultima chiamata aprint()il generatore sollevaStopIterationpoiché non ci sono più espressioniyield.Vediamo un altro esempio con
.send(). Il seguente generatore visualizza il saldo di un conto, ma il saldo può essere aggiornato:La funzione generatrice richiede un saldo iniziale quando viene chiamata. Il valore di
balancepuò cambiare mentre il generatore esegue il codice. Qualsiasi oggetto inviato al generatore con.send()viene assegnato aamount. Questa variabile saràNonese il generatore produce un valore senza che gli venga inviato alcun oggetto, oppure conterrà l'oggetto inviato tramite.send().L'iteratore generatore
current_balanceparte con un saldo di 100 $. Il generatore viene avviato chiamandonext(), che fa partire l'esecuzione del codice fino alla produzione del primo valore.Una volta avviato il generatore, è possibile far ripartire l'esecuzione usando
.send()invece dinext(). Il generatore aggiunge il valore inviato al saldo. Se non viene inviato alcun valore, come quando si chiama di nuovonext(), il generatore restituisce il saldo invariato.Produrre direttamente da un altro iterabile
I generatori Python possono anche produrre valori direttamente da un altro generatore o iterabile usando la sintassi
yield from. Vediamo un esempio di funzione generatrice che produce valori da una lista annidata:La funzione generatrice accetta una lista, che può includere al suo interno liste annidate. Il ciclo
foritera sugli elementi della lista. Ogni elemento della lista esterna è o un valore, in questo caso un intero, o un'altra lista. Quando l'elemento non è una lista, il generatore lo produce.Tuttavia, quando l'elemento è una lista, il generatore richiama ricorsivamente la funzione generatrice
flatten()con la lista interna come argomento. Questo crea un altro iteratore generatore, che usa la lista interna come sorgente dati. Se questa riga usasse un'espressioneyield, il primo generatore produrrebbe il secondo generatore. Invece, usandoyield from, il primo generatore produce i valori del secondo generatore.Riepilogo:
yieldvs.returnLe definizioni di funzione con
returneyieldsembrano simili, ma il loro comportamento è diverso. Riassumiamo le differenze chiave:Funzione normale
Funzione generatrice
Parola chiave
return (implicita se non usata esplicitamente)
yield
Chiamata
Esegue il codice fino a incontrare return, poi restituisce il valore finale
Crea un iteratore generatore
Terminazione
Terminata dall'istruzione return
Messa in pausa da yield, può essere ripresa più tardi
Valore restituito
Singolo oggetto (può essere una struttura dati)
Iteratore generatore
Espressione yield
Non applicabile (crea un'istruzione)
Valuta a None o al valore inviato con .send()
Casi d'uso
Ideale per restituire un risultato finale
Ideale per creare un flusso di dati, specialmente sequenze grandi o infinite
Conclusione
La parola chiave
yielddi Python è usata nelle funzioni per definire una funzione generatrice. Quando chiamate, queste funzioni creano iteratori generatori. I generatori sono un esempio di valutazione pigra in Python, in cui le espressioni vengono valutate quando serve il valore invece che al momento dell'esecuzione dell'espressione. Pertanto, l'espressioneyieldè utile per creare un flusso di dati in cui i valori vengono generati su richiesta senza bisogno di memorizzarli in memoria.Le considerazioni sull'efficienza sono importanti quando si lavora con grandi dataset che richiedono molte operazioni. Gli iteratori generatori di Python sono uno degli strumenti principali per manipolare grandi quantità di dati in modo efficiente.
Se vuoi saperne di più su Python, dai un'occhiata a questo percorso di carriera Python Developer.