Python Language introduzione

Esempio

Le espressioni del generatore sono simili a list, dictionary e set comprehensions, ma sono racchiuse tra parentesi. Le parentesi non devono essere presenti quando vengono utilizzate come unico argomento per una chiamata di funzione.

expression = (x**2 for x in range(10))

Questo esempio genera i primi 10 quadrati perfetti, incluso 0 (in cui x = 0).

Le funzioni del generatore sono simili alle funzioni regolari, tranne che hanno una o più dichiarazioni di yield nel loro corpo. Tali funzioni non possono return alcun valore (tuttavia è possibile return vuoti se si desidera arrestare anticipatamente il generatore).

def function():
    for x in range(10):
        yield x**2

Questa funzione del generatore è equivalente alla precedente espressione del generatore, emette lo stesso risultato.

Nota : tutte le espressioni del generatore hanno le loro funzioni equivalenti , ma non viceversa.


Un'espressione di generatore può essere utilizzata senza parentesi se entrambe le parentesi si ripetessero diversamente:

sum(i for i in range(10) if i % 2 == 0)   #Output: 20
any(x = 0 for x in foo)                   #Output: True or False depending on foo
type(a > b for a in foo if a % 2 == 1)    #Output: <class 'generator'>

Invece di:

sum((i for i in range(10) if i % 2 == 0))
any((x = 0 for x in foo))
type((a > b for a in foo if a % 2 == 1))

Ma no:

fooFunction(i for i in range(10) if i % 2 == 0,foo,bar)
return x = 0 for x in foo
barFunction(baz, a > b for a in foo if a % 2 == 1)

Chiamando una funzione generatore si genera un oggetto generatore , che può essere successivamente ripetuto. A differenza di altri tipi di iteratori, gli oggetti generatore possono essere attraversati solo una volta.

g1 = function()
print(g1)  # Out: <generator object function at 0x1012e1888>

Si noti che il corpo di un generatore non viene immediatamente eseguito: quando si chiama function() nell'esempio precedente, viene immediatamente restituito un oggetto generatore, senza eseguire nemmeno la prima istruzione print. Ciò consente ai generatori di consumare meno memoria rispetto alle funzioni che restituiscono una lista e consente la creazione di generatori che producono sequenze infinitamente lunghe.

Per questo motivo, i generatori vengono spesso utilizzati nella scienza dei dati e in altri contesti che coinvolgono grandi quantità di dati. Un altro vantaggio è che l'altro codice può utilizzare immediatamente i valori prodotti da un generatore, senza attendere che venga prodotta la sequenza completa.

Tuttavia, se è necessario utilizzare i valori prodotti da un generatore più di una volta, e se generarli costa più della memorizzazione, potrebbe essere preferibile memorizzare i valori ottenuti come list piuttosto che rigenerare la sequenza. Vedi 'Ripristino di un generatore' sotto per maggiori dettagli.

In genere un oggetto generatore viene utilizzato in un ciclo o in qualsiasi funzione che richiede un iterable:

for x in g1:
    print("Received", x)

# Output:
# Received 0
# Received 1
# Received 4
# Received 9
# Received 16
# Received 25
# Received 36
# Received 49
# Received 64
# Received 81

arr1 = list(g1)
# arr1 = [], because the loop above already consumed all the values.
g2 = function()
arr2 = list(g2)  # arr2 = [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Poiché gli oggetti generatore sono iteratori, uno può scorrere su di essi manualmente usando la funzione next() . Fare così restituirà i valori ottenuti uno per uno su ogni chiamata successiva.

Sotto il cofano, ogni volta che chiamate next() su un generatore, Python esegue istruzioni nel corpo della funzione generatore fino a quando non raggiunge la successiva dichiarazione di yield . A questo punto restituisce l'argomento del comando yield e ricorda il punto in cui è successo. Chiamando next() ancora una volta riprenderà l'esecuzione da quel punto e continuerà fino alla prossima dichiarazione di yield .

Se Python raggiunge la fine della funzione del generatore senza incontrare altri yield s, viene sollevata un'eccezione StopIteration (questo è normale, tutti gli iteratori si comportano allo stesso modo).

g3 = function()
a = next(g3)  # a becomes 0
b = next(g3)  # b becomes 1
c = next(g3)  # c becomes 2
...
j = next(g3)  # Raises StopIteration, j remains undefined

Si noti che in Python 2 gli oggetti del generatore avevano metodi .next() che potevano essere utilizzati per scorrere manualmente i valori ottenuti. In Python 3 questo metodo è stato sostituito con lo standard .__next__() per tutti gli iteratori.

Reimpostazione di un generatore

Ricorda che puoi solo scorrere gli oggetti generati da un generatore una sola volta . Se avete già iterato attraverso gli oggetti in uno script, ogni ulteriore tentativo di farlo produrrà None .

Se è necessario utilizzare gli oggetti generati da un generatore più di una volta, è possibile definire nuovamente la funzione generatore e utilizzarla una seconda volta oppure, in alternativa, è possibile memorizzare l'uscita della funzione generatore in un elenco al primo utilizzo. La ridefinizione della funzione del generatore sarà una buona opzione se si hanno a che fare con grandi volumi di dati e la memorizzazione di un elenco di tutti gli elementi di dati richiederebbe molto spazio sul disco. Viceversa, se inizialmente è costoso generare gli articoli, è preferibile memorizzare gli articoli generati in un elenco in modo da poterli riutilizzare.