38 Domanda: Cosa fa la parola chiave "rendimento"?

domanda creata a Thu, May 2, 2019 12:00 AM

A che cosa serve la parola chiave yield in Python? Che cosa fa?

Ad esempio, sto cercando di capire questo codice 1 :

 
def _get_child_candidates(self, distance, min_dist, max_dist):
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild
    if self._rightchild and distance + max_dist >= self._median:
        yield self._rightchild  

E questo è il chiamante:

 
result, candidates = [], [self]
while candidates:
    node = candidates.pop()
    distance = node._get_dist(obj)
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result

Cosa succede quando viene chiamato il metodo _get_child_candidates? È stata restituita una lista? Un singolo elemento? Si chiama di nuovo? Quando verranno interrotte le chiamate successive?


1. Questo pezzo di codice è stato scritto da Jochen Schulz (jrschulz), che ha creato una grande libreria Python per gli spazi metrici. Questo è il link alla fonte completa: mspace del modulo .

    
9314
30 risposte                              30                         

Per capire cosa fa yield, devi capire cosa sono generatori . E prima che tu possa capire i generatori, devi capire iterables .

iterabili

Quando crei un elenco, puoi leggere i suoi elementi uno per uno. La lettura dei suoi articoli uno ad uno si chiama iterazione:

 
>>> mylist = [1, 2, 3]
>>> for i in mylist:
...    print(i)
1
2
3

mylist è un iterabile . Quando usi una comprensione di lista, crei un elenco, e quindi un iterabile:

 
>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
...    print(i)
0
1
4

Tutto ciò che è possibile utilizzare "for... in..." è un iterabile; lists, strings, file ...

Questi iterabili sono utili perché puoi leggerli quanto vuoi, ma memorizzi tutti i valori in memoria e questo non è sempre quello che vuoi quando hai molti valori.

Generatori

I generatori sono iteratori, una specie di iterabile puoi solo scorrere una volta sola . I generatori non memorizzano tutti i valori in memoria, generano i valori al volo :

 
>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
...    print(i)
0
1
4

È lo stesso eccetto che hai usato () invece di []. MA, non puoi eseguire for i in mygenerator una seconda volta poiché i generatori possono essere utilizzati solo una volta: calcolano 0, quindi dimenticano e calcolano 1 e terminano il calcolo 4, uno per uno.

Resa

yield è una parola chiave che viene utilizzata come return, ad eccezione della funzione che restituirà un generatore.

 
>>> def createGenerator():
...    mylist = range(3)
...    for i in mylist:
...        yield i*i
...
>>> mygenerator = createGenerator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
...     print(i)
0
1
4

Ecco un esempio inutile, ma è utile quando sai che la tua funzione restituirà un enorme set di valori che dovrai leggere solo una volta.

Per padroneggiare yield, devi capire che quando chiami la funzione, il codice che hai scritto nel corpo della funzione non viene eseguito. La funzione restituisce solo l'oggetto generatore, questo è un po 'complicato : -)

Quindi, il codice continuerà da dove è stato interrotto ogni volta che for utilizza il generatore.

Ora la parte difficile:

La prima volta che il for chiama l'oggetto generatore creato dalla tua funzione, eseguirà il codice nella tua funzione dall'inizio fino a raggiungere yield, quindi restituirà il primo valore del ciclo. Quindi, ogni altra chiamata eseguirà il ciclo che hai scritto nella funzione ancora una volta e restituirà il valore successivo, fino a quando non ci sarà alcun valore da restituire.

Il generatore è considerato vuoto una volta che la funzione è stata eseguita, ma non ha più colpito yield. Può essere perché il ciclo si è concluso, o perché non si soddisfa più un "if/else".


Il tuo codice ha spiegato

Generatore:

 
# Here you create the method of the node object that will return the generator
def _get_child_candidates(self, distance, min_dist, max_dist):

    # Here is the code that will be called each time you use the generator object:

    # If there is still a child of the node object on its left
    # AND if distance is ok, return the next child
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild

    # If there is still a child of the node object on its right
    # AND if distance is ok, return the next child
    if self._rightchild and distance + max_dist >= self._median:
        yield self._rightchild

    # If the function arrives here, the generator will be considered empty
    # there is no more than two values: the left and the right children

Caller:

 
# Create an empty list and a list with the current object reference
result, candidates = list(), [self]

# Loop on candidates (they contain only one element at the beginning)
while candidates:

    # Get the last candidate and remove it from the list
    node = candidates.pop()

    # Get the distance between obj and the candidate
    distance = node._get_dist(obj)

    # If distance is ok, then you can fill the result
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)

    # Add the children of the candidate in the candidates list
    # so the loop will keep running until it will have looked
    # at all the children of the children of the children, etc. of the candidate
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))

return result

Questo codice contiene diverse parti intelligenti:

  • Il ciclo scorre su una lista, ma l'elenco si espande mentre il ciclo viene iterato :-) È un modo conciso per esaminare tutti questi dati annidati anche se è un po 'pericoloso poiché si può finire con un ciclo infinito. In questo caso, candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) esaurisce tutti i valori del generatore, ma while continua a creare nuovi oggetti generatore che produrranno valori diversi dai precedenti poiché non è applicato sullo stesso nodo.

  • Il metodo extend() è un metodo elenco oggetti che si aspetta un iterabile e aggiunge i suoi valori all'elenco.

Di solito passiamo una lista ad esso:

 
>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]

Ma nel tuo codice viene generato un generatore, il che è positivo perché:

  1. Non è necessario leggere i valori due volte.
  2. Potresti avere molti figli e non li vuoi tutti salvati in memoria.

E funziona perché a Python non interessa se l'argomento di un metodo è un elenco o meno. Python prevede iterabili, quindi funzionerà con stringhe, elenchi, tuple e generatori! Si chiama duck typing ed è una delle ragioni per cui Python è così cool. Ma questa è un'altra storia, per un'altra domanda ...

Puoi fermarti qui o leggere un po 'bit per vedere un uso avanzato di un generatore:

Controllo dell'esaurimento del generatore

 
>>> class Bank(): # Let's create a bank, building ATMs
...    crisis = False
...    def create_atm(self):
...        while not self.crisis:
...            yield "$100"
>>> hsbc = Bank() # When everything's ok the ATM gives you as much as you want
>>> corner_street_atm = hsbc.create_atm()
>>> print(corner_street_atm.next())
$100
>>> print(corner_street_atm.next())
$100
>>> print([corner_street_atm.next() for cash in range(5)])
['$100', '$100', '$100', '$100', '$100']
>>> hsbc.crisis = True # Crisis is coming, no more money!
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # It's even true for new ATMs
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # The trouble is, even post-crisis the ATM remains empty
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # Build a new one to get back in business
>>> for cash in brand_new_atm:
...    print cash
$100
$100
$100
$100
$100
$100
$100
$100
$100
...

Nota: per Python 3, usa print(corner_street_atm.__next__()) o print(next(corner_street_atm))

Può essere utile per varie cose come controllare l'accesso a una risorsa.

Itertools, il tuo migliore amico

Il modulo itertools contiene funzioni speciali per manipolare i iterabili. Hai mai desiderato duplicare un generatore? Catena due generatori? Raggruppa i valori in un elenco annidato con un unico elemento? Map / Zip senza creare un altro elenco?

Quindi solo import itertools.

Un esempio? Vediamo i possibili ordini di arrivo per una corsa di quattro cavalli:

 
>>> horses = [1, 2, 3, 4]
>>> races = itertools.permutations(horses)
>>> print(races)
<itertools.permutations object at 0xb754f1dc>
>>> print(list(itertools.permutations(horses)))
[(1, 2, 3, 4),
 (1, 2, 4, 3),
 (1, 3, 2, 4),
 (1, 3, 4, 2),
 (1, 4, 2, 3),
 (1, 4, 3, 2),
 (2, 1, 3, 4),
 (2, 1, 4, 3),
 (2, 3, 1, 4),
 (2, 3, 4, 1),
 (2, 4, 1, 3),
 (2, 4, 3, 1),
 (3, 1, 2, 4),
 (3, 1, 4, 2),
 (3, 2, 1, 4),
 (3, 2, 4, 1),
 (3, 4, 1, 2),
 (3, 4, 2, 1),
 (4, 1, 2, 3),
 (4, 1, 3, 2),
 (4, 2, 1, 3),
 (4, 2, 3, 1),
 (4, 3, 1, 2),
 (4, 3, 2, 1)]

Comprensione dei meccanismi interni di iterazione

L'iterazione è un processo che implica iterabili (che implementano il metodo __iter__()) e iteratori (che implementa il metodo __next__()). Iterables sono tutti gli oggetti da cui è possibile ottenere un iteratore. Iteratori sono oggetti che ti permettono di scorrere su iterabili.

C'è dell'altro in questo articolo su come funzionano i loop for .

    
13495
2019-05-02 19: 34: 54Z
  1. Tutti gli iteratori possono essere ripetuti solo una volta, non solo quelli prodotti dalle funzioni del generatore. Se non mi credi, chiama iter() su qualsiasi oggetto iterabile e prova a ripetere il risultato più volte.
    2015-03-03 22: 54: 31Z
  2. @ Craicerjack Hai mescolato i tuoi termini. Un iterabile è qualcosa con un metodo __iter__. Un iteratore è il risultato della chiamata iter() su un iterabile. Gli iteratori possono essere ripetuti solo una volta.
    2015-08-24 20: 52: 18Z
  3. yield non è magico come suggerisce questa risposta. Quando si chiama una funzione che contiene un'istruzione yield ovunque, si ottiene un oggetto generatore, ma non viene eseguito alcun codice. Quindi, ogni volta che estrai un oggetto dal generatore, Python esegue il codice nella funzione finché non arriva a un'istruzione yield, quindi mette in pausa e consegna l'oggetto. Quando si estrae un altro oggetto, Python riprende subito dopo il yield e continua fino a raggiungere un altro yield (spesso lo stesso, ma uno dopo). Questo continua finché la funzione non si esaurisce alla fine, a quel punto il generatore viene considerato esaurito.
    2017-05-23 21: 41: 53Z
  4. "Questi iterabili sono utili ... ma memorizzi tutti i valori in memoria e questo non è sempre quello che vuoi", è sbagliato o confuso. Un iteratore restituisce un iteratore quando chiama iter () sull'iterazione, e un iteratore non deve sempre memorizzare i suoi valori in memoria, a seconda dell'implementazione del metodo iter , può anche generare valori nella sequenza su richiesta.
    2018-02-15 19: 21: 11Z

Collegamento per comprendere yield

Quando vedi una funzione con le dichiarazioni yield, applica questo semplice trucco per capire cosa succederà:

  1. Inserisci una riga result = [] all'inizio della funzione.
  2. Sostituisci ogni yield expr con result.append(expr).
  3. Inserisci una riga return result nella parte inferiore della funzione.
  4. Yay - non più dichiarazioni yield! Leggi e calcola il codice.
  5. Confronta la funzione con la definizione originale.

Questo trucco potrebbe darti un'idea della logica alla base della funzione, ma ciò che effettivamente accade con yield è significativamente diverso da ciò che accade nell'approccio basato sull'elenco. In molti casi l'approccio di rendimento sarà molto più efficiente e veloce anche nella memoria. In altri casi questo trucco ti farà rimanere bloccato in un ciclo infinito, anche se la funzione originale funziona perfettamente. Continua a leggere per saperne di più ...

Non confondere i tuoi Iterables, Iterators e Generators

Innanzitutto, il protocollo iteratore - quando scrivi

 
for x in mylist:
    ...loop body...

Python esegue i seguenti due passi:

  1. Ottiene un iteratore per mylist:

    Chiama iter(mylist) - > questo restituisce un oggetto con un metodo next() (o __next__() in Python 3).

    [Questo è il passo di cui la maggior parte della gente si dimentica di dirti]

  2. Utilizza l'iteratore per eseguire il loop degli elementi:

    Continua a chiamare tIl metodo next() sull'iteratore è ritornato dal passaggio 1. Il valore di ritorno da next() viene assegnato a x e il corpo del loop viene eseguito. Se viene sollevata un'eccezione StopIteration dall'interno di next(), significa che non ci sono più valori nell'iteratore e il ciclo viene chiuso.

La verità è che Python esegue i suddetti due passaggi ogni volta che vuole eseguire il loop del contenuto di un oggetto - quindi potrebbe essere un ciclo for, ma potrebbe anche essere un codice come otherlist.extend(mylist) (dove otherlist è un elenco Python).

Qui mylist è un iterabile perché implementa il protocollo iteratore. In una classe definita dall'utente, è possibile implementare il metodo __iter__() per rendere iterabili le istanze della classe. Questo metodo dovrebbe restituire un iteratore . Un iteratore è un oggetto con un metodo next(). È possibile implementare sia __iter__() che next() sulla stessa classe e avere __iter__() restituire self. Ciò funzionerà per casi semplici, ma non quando vuoi che due iteratori passino sullo stesso oggetto nello stesso momento.

Quindi questo è il protocollo iteratore, molti oggetti implementano questo protocollo:

  1. Elenchi, dizionari, tuple, insiemi, file incorporati
  2. Classi definite dall'utente che implementano __iter__().
  3. Generatori.

Si noti che un loop for non conosce il tipo di oggetto con cui si sta occupando - segue solo il protocollo iteratore ed è felice di ottenere articoli dopo l'articolo poiché chiama next(). Gli elenchi integrati restituiscono i loro elementi uno per uno, i dizionari restituiscono i tasti uno per uno, i file restituiscono le linee una per una, ecc. E i generatori restituiscono ... bene è qui che entra in gioco yield:

 
def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

Invece delle dichiarazioni yield, se avessi tre istruzioni return in f123(), solo il primo verrebbe eseguito e la funzione uscirebbe. Ma il f123() non è una funzione ordinaria. Quando viene chiamato f123(), non restituisce nessuno dei valori nelle dichiarazioni di rendimento! Restituisce un oggetto generatore. Inoltre, la funzione non esce realmente - entra in uno stato sospeso. Quando il ciclo for tenta di eseguire il looping sull'oggetto generatore, la funzione riprende dallo stato sospeso nella riga successiva dopo che il yield da cui è stato restituito, esegue la riga successiva del codice, in questo caso un'istruzione yield e la restituisce come il prossimo oggetto. Questo accade fino a quando la funzione non si chiude, a quel punto il generatore genera StopIteration e il ciclo esce.

Quindi l'oggetto generatore è un po 'come un adattatore - ad una estremità esibisce il protocollo iteratore, esponendo i metodi __iter__() e next() per mantenere il ciclo for felice. All'altro capo, tuttavia, esegue la funzione quel tanto che basta per ricavarne il valore successivo e la rimette in modalità sospesa.

Perché utilizzare i generatori?

Di solito puoi scrivere codice che non usa generatori ma implementa la stessa logica. Un'opzione è usare il "trucco" di elenco temporaneo che ho menzionato prima. Ciò non funzionerà in tutti i casi, ad es. se hai loop infiniti, o può fare un uso inefficiente della memoria quando hai una lista molto lunga. L'altro approccio consiste nell'implementare una nuova classe iterabile SomethingIter che mantiene lo stato nei membri dell'istanza ed esegue il successivo passo logico nel suo metodo next() (o __next__() in Python 3). A seconda della logica, il codice all'interno del metodo next() può sembrare molto complesso e soggetto a bug. Qui i generatori forniscono una soluzione semplice e pulita.

    
1810
2019-04-24 09: 37: 06Z
  1. "Quando vedi una funzione con dichiarazioni di rendimento, applica questo semplice trucco per capire cosa accadrà" Questo non ignora completamente il fatto che puoi send in un generatore, che è una parte enorme del punto di generatori?
    2017-06-17 22: 41: 34Z
  2. "potrebbe essere un ciclo for, ma potrebbe anche essere un codice come otherlist.extend(mylist)" - > Questo non è corretto extend() modifica l'elenco sul posto e non restituisce un iterabile. Il tentativo di eseguire il loop su otherlist.extend(mylist) fallirà con un TypeError perché extend() restituisce implicitamente None e non è possibile eseguire il loop su None.
    2017-09-14 14: 48: 17Z
  3. @ pedro Hai frainteso quella frase. Significa che python esegue i due passaggi menzionati su mylist (non su otherlist) quando si esegue otherlist.extend(mylist).
    2017-12-26 18: 53: 57Z

Pensaci in questo modo:

Un iteratore è solo un termine dal suono elaborato per un oggetto che ha un metodo next(). Quindi una funzione resa diventa qualcosa di simile a questo:

Versione originale:

 
def some_function():
    for i in xrange(4):
        yield i

for i in some_function():
    print i

Questo è fondamentalmente ciò che l'interprete Python fa con il codice precedente:

 
class it:
    def __init__(self):
        # Start at -1 so that we get 0 when we add 1 below.
        self.count = -1

    # The __iter__ method will be called once by the 'for' loop.
    # The rest of the magic happens on the object returned by this method.
    # In this case it is the object itself.
    def __iter__(self):
        return self

    # The next method will be called repeatedly by the 'for' loop
    # until it raises StopIteration.
    def next(self):
        self.count += 1
        if self.count < 4:
            return self.count
        else:
            # A StopIteration exception is raised
            # to signal that the iterator is done.
            # This is caught implicitly by the 'for' loop.
            raise StopIteration

def some_func():
    return it()

for i in some_func():
    print i

Per ulteriori informazioni su cosa succede dietro le quinte, il ciclo for può essere riscritto a questo:

 
iterator = some_func()
try:
    while 1:
        print iterator.next()
except StopIteration:
    pass

Ha più senso o ti confonde di più? :)

Devo notare che questa è una semplificazione eccessiva a scopi illustrativi. :)

    
476
2019-05-07 13: 28: 35Z
  1. __getitem__ potrebbe essere definito al posto di __iter__. Ad esempio: class it: pass; it.__getitem__ = lambda self, i: i*10 if i < 10 else [][0]; for i in it(): print(i), Stampa: 0, 10, 20, ..., 90
    2008-10-25 02: 03: 38Z
  2. Ho provato questo esempio in Python 3.6 e se creo iterator = some_function(), la variabile iterator non ha più una funzione chiamata next(), ma solo una funzione __next__(). Ho pensato di parlarne.
    2017-05-06 14: 37: 55Z

La parola chiave yield è ridotta a due semplici fatti:

  1. Se il compilatore rileva la parola chiave yield ovunque all'interno di una funzione, quella funzione non viene più restituita tramite l'istruzione return. Invece , immediatamente restituisce un oggetto lazy "in sospeso" chiamato un generatore
  2. Un generatore è iterabile. Cos'è un iterabile ? È un po 'come un list o set o range o una vista dict, con un protocollo integrato per visitare ogni elemento in un certo ordine .

In poche parole: un generatore è un elenco pigro, in pendenza incrementale e yield consente di utilizzare la notazione della funzione per programmare i valori dell'elenco il generatore dovrebbe incrementare sputare.

 
generator = myYieldingFunction(...)
x = list(generator)

   generator
       v
[x[0], ..., ???]

         generator
             v
[x[0], x[1], ..., ???]

               generator
                   v
[x[0], x[1], x[2], ..., ???]

                       StopIteration exception
[x[0], x[1], x[2]]     done

list==[x[0], x[1], x[2]]

Esempio

Definiamo una funzione makeRange che è proprio come il range di Python. Chiamando makeRange(n) RESTITUISCE UN GENERATORE:

 
def makeRange(n):
    # return 0,1,2,...,n-1
    i = 0
    while i < n:
        yield i
        i += 1

>>> makeRange(5)
<generator object makeRange at 0x19e4aa0>

Per forzare il generatore a restituire immediatamente i suoi valori in sospeso, puoi passarlo a list() (proprio come qualsiasi altro iterabile):

 
>>> list(makeRange(5))
[0, 1, 2, 3, 4]

Confronto di esempio con "solo restituendo un elenco"

L'esempio sopra può essere pensato semplicemente come la creazione di un elenco a cui si aggiunge e che si restituisce:

 
# list-version                   #  # generator-version
def makeRange(n):                #  def makeRange(n):
    """return [0,1,2,...,n-1]""" #~     """return 0,1,2,...,n-1"""
    TO_RETURN = []               #>
    i = 0                        #      i = 0
    while i < n:                 #      while i < n:
        TO_RETURN += [i]         #~         yield i
        i += 1                   #          i += 1  ## indented
    return TO_RETURN             #>

>>> makeRange(5)
[0, 1, 2, 3, 4]

C'è una grande differenza, però; guarda l'ultima sezione.


Come potresti utilizzare i generatori

Un iterable è l'ultima parte di una list comprehension, e tutti i generatori sono iterabili, quindi vengono spesso utilizzati in questo modo:

 
#                   _ITERABLE_
>>> [x+10 for x in makeRange(5)]
[10, 11, 12, 13, 14]

Per avere una sensazione migliore per i generatori, puoi giocare con il modulo itertools (assicurati di usare chain.from_iterable anziché chain quando richiesto). Ad esempio, potresti anche utilizzare i generatori per implementare elenchi pigri infinitamente lunghi come itertools.count(). Puoi implementare il tuo def enumerate(iterable): zip(count(), iterable), o in alternativa farlo con la parola chiave yield in un ciclo while.

Nota: i generatori possono essere effettivamente utilizzati per molte altre cose, come implementare le coroutine o programmazione non deterministica o altre cose eleganti. Tuttavia, il punto di vista delle "liste pigre" che presento qui è l'uso più comune che troverai.


Dietro le quinte

Ecco come funziona il "protocollo di iterazione Python". Cioè, cosa sta succedendo quando fai list(makeRange(5)). Questo è ciò che descrivo precedentemente come un "elenco pigro e incrementale".

 
>>> x=iter(range(5))
>>> next(x)
0
>>> next(x)
1
>>> next(x)
2
>>> next(x)
3
>>> next(x)
4
>>> next(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

La funzione incorporata next() chiama solo la funzione oggetti .next(), che è una parte del "protocollo di iterazione" e si trova su tutti gli iteratori. Puoi utilizzare manualmente la funzione next() (e altre parti del protocollo di iterazione) per implementare cose di fantasia, di solito a scapito della leggibilità, quindi cerca di evitare di farlo ...


Minutiae

Normalmente, la maggior parte delle persone non si preoccuperebbe delle seguenti distinzioni e probabilmente vorrebbe smettere di leggere qui.

In Python-speak, un iterabile è un oggetto che "capisce il concetto di un ciclo for" come una lista [1,2,3], e un iteratore è un'istanza specifica del ciclo di loop richiesto come [1,2,3].__iter__(). Un generatore è esattamente uguale a qualsiasi iteratore, tranne per il modo in cui è stato scritto (con la sintassi della funzione).

Quando si richiede un iteratore da un elenco, viene creato un nuovo iteratore. Tuttavia, quando richiedi un iteratore da un iteratore (cosa che raramente faresti), ti dà solo una copia di se stesso.

Quindi, nell'improbabile caso che tu non riesca a fare qualcosa di simile ...

 
> x = myRange(5)
> list(x)
[0, 1, 2, 3, 4]
> list(x)
[]

... quindi ricorda che un generatore è un iteratore ; cioè, è una tantum. Se si desidera riutilizzarlo, è necessario chiamare nuovamente il myRange(...). Se è necessario utilizzare il risultato due volte, convertire il risultato in un elenco e memorizzarlo in una variabile x = list(myRange(5)). Coloro che hanno assolutamente bisogno di clonare un generatore (per esempio, chi sta facendo terrificante metaprogrammazione hacker) possono usare itertools.tee se assolutamente necessario, dal momento che il programma di copia iteratore Python PEP proposta di standard è stata posticipata.

    
402
2017-03-19 08: 07: 35Z
  

Che cosa fa la parola chiave yield in Python?

Rispondi a Profilo /Riepilogo

  • Una funzione con yield , quando chiamata , restituisce un generatore .
  • I generatori sono iteratori perché implementano il protocollo iteratore , quindi puoi eseguirne l'iterazione.
  • Un generatore può anche essere inviato informazioni , rendendo concettualmente una coroutine .
  • In Python 3, puoi delegare da un generatore a un altro in entrambe le direzioni con yield from .
  • (l'Appendice critica un paio di risposte, inclusa quella superiore, e discute l'uso di return in un generatore.)

Generatori:

yield è legale solo all'interno di una definizione di funzione e l'inclusione di yield in una definizione di funzione fa sì che restituisca un generatore.

L'idea per i generatori viene da altre lingue (vedi nota 1) con diverse implementazioni. In Python's Generators, l'esecuzione del codice è congelato sul punto della resa. Quando viene chiamato il generatore (i metodi sono discussi di seguito) l'esecuzione riprende e quindi si blocca alla resa successiva.

yield fornisce un modo semplice di implementare il protocollo iteratore , definito dai due seguenti metodi:  __iter__ e next (Python 2) o __next__ (Python 3). Entrambi questi metodi rendere un oggetto un iteratore che è possibile digitare con la Base astratta Iterator Classe dal modulo collections.

 
>>> def func():
...     yield 'I am'
...     yield 'a generator!'
... 
>>> type(func)                 # A function with yield is still a function
<type 'function'>
>>> gen = func()
>>> type(gen)                  # but it returns a generator
<type 'generator'>
>>> hasattr(gen, '__iter__')   # that's an iterable
True
>>> hasattr(gen, 'next')       # and with .next (.__next__ in Python 3)
True                           # implements the iterator protocol.

Il tipo di generatore è un sottotipo di iteratore:

 
>>> import collections, types
>>> issubclass(types.GeneratorType, collections.Iterator)
True

E se necessario, possiamo digitare type-check in questo modo:

 
>>> isinstance(gen, types.GeneratorType)
True
>>> isinstance(gen, collections.Iterator)
True

Una funzionalità di Iterator è che una volta esaurito , puoi puoi riutilizzarlo o ripristinarlo:

 
>>> list(gen)
['I am', 'a generator!']
>>> list(gen)
[]

Dovrai farne un altro se vuoi usare di nuovo la sua funzionalità (vedi nota 2):

 
>>> list(func())
['I am', 'a generator!']

Uno può fornire dati a livello di codice, ad esempio:

 
def func(an_iterable):
    for item in an_iterable:
        yield item

Il generatore semplice di cui sopra è anche equivalente al seguente - a partire da Python 3.3 (e non disponibile in Python 2), puoi usare yield from :

 
def func(an_iterable):
    yield from an_iterable

Tuttavia, yield from consente anche la delega ai subgenerators, che sarà spiegato nella seguente sezione sulla delega cooperativa con sub-coroutine.

coroutine:

yield forma un'espressione che consente l'invio di dati nel generatore (vedi nota 3)

Ecco un esempio, prendi nota della variabile received, che punterà ai dati inviati al generatore:

 
def bank_account(deposited, interest_rate):
    while True:
        calculated_interest = interest_rate * deposited 
        received = yield calculated_interest
        if received:
            deposited += received


>>> my_account = bank_account(1000, .05)

Innanzitutto, dobbiamo accodare il generatore con la funzione incorporata, next . Lo farà chiamare il metodo appropriato next o __next__, a seconda della versione di Python che stai utilizzando:

 
>>> first_year_interest = next(my_account)
>>> first_year_interest
50.0

E ora possiamo inviare dati al generatore. ( L'invio di None è lo stesso che chiamare next .):

 
>>> next_year_interest = my_account.send(first_year_interest + 1000)
>>> next_year_interest
102.5

Delega cooperativa a sottocoroutine con yield from

Ora, ricorda che yield from è disponibile in Python 3. Ciò ci consente di delegare coroutine a una subcoroutine:

 
def money_manager(expected_rate):
    under_management = yield     # must receive deposited value
    while True:
        try:
            additional_investment = yield expected_rate * under_management 
            if additional_investment:
                under_management += additional_investment
        except GeneratorExit:
            '''TODO: write function to send unclaimed funds to state'''
        finally:
            '''TODO: write function to mail tax info to client'''


def investment_account(deposited, manager):
    '''very simple model of an investment account that delegates to a manager'''
    next(manager) # must queue up manager
    manager.send(deposited)
    while True:
        try:
            yield from manager
        except GeneratorExit:
            return manager.close()

E ora possiamo delegare funzionalità a un sub-generatore e può essere usato da un generatore proprio come sopra:

 
>>> my_manager = money_manager(.06)
>>> my_account = investment_account(1000, my_manager)
>>> first_year_return = next(my_account)
>>> first_year_return
60.0
>>> next_year_return = my_account.send(first_year_return + 1000)
>>> next_year_return
123.6

Puoi leggere di più sulla semantica precisa di yield from in PEP 380 .

Altri metodi: chiudi e getta ​​h2>

Il metodo close solleva GeneratorExit nel punto della funzione l'esecuzione è stata congelata. Questo sarà anche chiamato da __del__ così tu puoi inserire qualsiasi codice di pulizia in cui gestisci il GeneratorExit:

 
>>> my_account.close()

Puoi anche lanciare un'eccezione che può essere gestita nel generatore o propagati all'utente:

 
>>> import sys
>>> try:
...     raise ValueError
... except:
...     my_manager.throw(*sys.exc_info())
... 
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
  File "<stdin>", line 2, in <module>
ValueError

Conclusione

Credo di aver coperto tutti gli aspetti della seguente domanda:

  

Che cosa fa la parola chiave yield in Python?

Si scopre che yield fa molto. Sono sicuro che potrei aggiungere ancora di più esempi completi a questo. Se vuoi di più o avere qualche critica costruttiva, fammi sapere commentando qui di seguito.


Appendice:

Critica della risposta in alto /accettata **

  • È confuso su ciò che rende iterable , usando solo un elenco come esempio. Vedi i miei riferimenti sopra, ma in breve: un iterabile ha un metodo __iter__ che restituisce un iteratore . Un iteratore fornisce un .next (metodo Python 2 o .__next__ (Python 3), che viene implicitamente chiamato da for loop fino a quando non solleva StopIteration, e una volta fatto, continuerà a farlo.
  • Quindi usa un'espressione di generatore per descrivere cosa è un generatore. Poiché un generatore è semplicemente un modo conveniente per creare un iteratore , confonde solo la questione e non abbiamo ancora ottenuto la parte yield.
  • In Controllo dell'esaurimento del generatore chiama il metodo .next, quando invece dovrebbe utilizzare la funzione incorporata, next. Sarebbe uno strato appropriato di riferimento indiretto, perché il suo codice non funziona in Python 3.
  • itertools? Questo non era rilevante per ciò che fa yield.
  • Nessuna discussione sui metodi forniti da yield insieme alla nuova funzionalità yield from in Python 3. La risposta in alto /accettato è una risposta molto incompleta.

Critica di risposta che suggerisce yield in un'espressione o comprensione di generatore.

Attualmente la grammatica consente qualsiasi espressione in una comprensione di lista.

 
expr_stmt: testlist_star_expr (annassign | augassign (yield_expr|testlist) |
                     ('=' (yield_expr|testlist_star_expr))*)
...
yield_expr: 'yield' [yield_arg]
yield_arg: 'from' test | testlist

Poiché yield è un'espressione, è stato pubblicizzato da alcuni come interessanti da usare nelle espressioni o nell'espressione di un generatore - nonostante non citi casi di utilizzo particolarmente buoni.

Gli sviluppatori principali di CPython sono discutendo sulla deprecazione della sua tolleranza . Ecco un post pertinente dalla mailing list:

  

Il 30 gennaio 2017 alle 19:05, Brett Cannon ha scritto:

     
    

Il 29 gennaio 2017 alle 16:39 Craig Rodrigues ha scritto:

         
      

Sto bene con entrambi gli approcci. Lasciando le cose come sono in Python 3       non va bene, IMHO.

    
         

Il mio voto è un errore Syntax poiché non ottieni ciò che ti aspetti     la sintassi.

  
     

Sono d'accordo che per noi è un posto sensato finire come qualsiasi codice   affidarsi al comportamento attuale è davvero troppo intelligente per essere   mantenibile.

     

In termini di accesso, probabilmente vorremmo:

     
  • SyntaxWarning o DeprecationWarning in 3.7
  •   
  • Avviso Py3k in 2.7.x
  •   
  • SyntaxError in 3.8
  •   

Saluti, Nick.

     

- Nick Coghlan | ncoghlan su gmail.com | Brisbane, Australia

Inoltre, esiste un numero in sospeso (10544) che sembra indicare la direzione di questo mai essere una buona idea (PyPy, un'implementazione Python scritta in Python, sta già sollevando avvisi di sintassi.)

In conclusione, finché gli sviluppatori di CPython non ci dicono diversamente: Non inserire yield in un'espressione o comprensione di generatore.

L'istruzione return in un generatore

In Python 2 :

  

In una funzione generatore, l'istruzione return non può includere un expression_list. In quel contesto, un return nudo indica che il generatore è finito e farà innalzare StopIteration.

Un expression_list è di baseogni numero di espressioni separate da virgole - in sostanza, in Python 2, puoi fermare il generatore con return, ma non puoi restituire un valore.

In Python 3 :

  

In una funzione di generatore, l'istruzione return indica che il generatore è finito e farà innalzare StopIteration. Il valore restituito (se presente) viene utilizzato come argomento per costruire StopIteration e diventa l'attributo StopIteration.value.

Note

  1. Le lingue CLU, Sather e Icon sono state referenziate nella proposta per introdurre il concetto di generatori in Python. L'idea generale è che una funzione può mantenere lo stato interno e produrre un intermedio punti dati su richiesta dell'utente. Questo ha promesso di essere superiore in termini di prestazioni ad altri approcci, incluso il threading di Python , che non è nemmeno disponibile su alcuni sistemi.

  2. Ciò significa, ad esempio, che xrange oggetti (range in Python 3) non sono Iterator s, anche se sono iterabili, perché possono essere riutilizzati. Come gli elenchi, i loro metodi __iter__ restituiscono oggetti iteratore.

  3. yield è stato originariamente introdotto come dichiarazione, il che significa che potrebbe apparire solo all'inizio di una riga in un blocco di codice. Ora yield crea un'espressione di resa. https://docs.python.org/2/reference/simple_stmts .html # grammatica-token-yield_stmt Questa modifica è stata proposta per consentire un utente per inviare dati nel generatore proprio come uno potrebbe riceverlo. Per inviare dati, bisogna essere in grado di assegnarlo a qualcosa, e per questo, una dichiarazione non funzionerà.

321
2017-12-06 20: 20: 40Z

yield è come return - restituisce qualsiasi cosa tu gli dica (come generatore). La differenza è che la prossima volta che si chiama il generatore, l'esecuzione inizia dall'ultima chiamata all'istruzione yield. A differenza del ritorno, il frame dello stack non viene ripulito quando si verifica un rendimento, tuttavia il controllo viene trasferito al chiamante, quindi il suo stato riprenderà al prossimo richiamo della funzione.

Nel caso del tuo codice, la funzione get_child_candidates si comporta come un iteratore in modo che quando estendi la tua lista, aggiunge un elemento alla volta al nuovo elenco.

list.extend chiama un iteratore finché non è esaurito. Nel caso dell'esempio di codice che hai postato, sarebbe molto più semplice restituire una tupla e aggiungerla all'elenco.

    
276
2019-01-24 09: 39: 59Z
  1. Questo è vicino, ma non è corretto. Ogni volta che chiami una funzione con una dichiarazione di rendimento, restituisce un oggetto generatore nuovo di zecca. È solo quando chiami il metodo .next () di quel generatore che riprende l'esecuzione dopo l'ultima resa.
    2008-10-24 18: 11: 04Z

C'è una cosa in più da citare: una funzione che produce non deve necessariamente terminare. Ho scritto un codice come questo:

 
def fib():
    last, cur = 0, 1
    while True: 
        yield cur
        last, cur = cur, last + cur

Quindi posso usarlo in un altro codice come questo:

 
for f in fib():
    if some_condition: break
    coolfuncs(f);

Aiuta davvero a semplificare alcuni problemi e facilita il lavoro con alcune cose.

    
209
2013-04-21 15: 42: 14Z

Per coloro che preferiscono un esempio di lavoro minimo, medita su questa sessione interattiva di Python:

 
>>> def f():
...   yield 1
...   yield 2
...   yield 3
... 
>>> g = f()
>>> for i in g:
...   print i
... 
1
2
3
>>> for i in g:
...   print i
... 
>>> # Note that this time nothing was printed
    
184
2019-05-08 12: 20: 10Z

TL; DR

Invece di questo:

 
def square_list(n):
    the_list = []                         # Replace
    for x in range(n):
        y = x * x
        the_list.append(y)                # these
    return the_list                       # lines

fai questo:

 
def square_yield(n):
    for x in range(n):
        y = x * x
        yield y                           # with this one.

Ogni volta che ti ritrovi a costruire una lista da zero, yield ogni pezzo invece.

Questo è stato il mio primo momento "aha" con rendimento.


yield è un modo zuccherino per dire

  

costruisci una serie di cose

Stesso comportamento:

 
>>> for square in square_list(4):
...     print(square)
...
0
1
4
9
>>> for square in square_yield(4):
...     print(square)
...
0
1
4
9

Comportamento diverso:

Il rendimento è single-pass : puoi eseguire una sola volta l'iterazione. Quando una funzione ha un rendimento, la chiamiamo funzione generatore . E un iteratore è ciò che restituisce. Quei termini sono rivelatori. Perdiamo la comodità di un contenitore, ma otteniamo la potenza di una serie calcolata in base alle esigenze e arbitrariamente lunga.

Il rendimento è pigro , mette fuori calcolo. Una funzione con una resa in esso in realtà non viene eseguita affatto quando la chiami. Restituisce un oggetto iteratore che ricorda da dove era stato interrotto. Ogni volta che chiamate next() sull'iterazione iteratore (questo avviene in un ciclo for), i pollici avanzano al rendimento successivo. return solleva StopIteration e termina la serie (questa è la fine naturale di un ciclo for).

Il rendimento è versatile . I dati non devono essere memorizzati tutti insieme, possono essere resi disponibili uno alla volta. Può essere infinito.

 
>>> def squares_all_of_them():
...     x = 0
...     while True:
...         yield x * x
...         x += 1
...
>>> squares = squares_all_of_them()
>>> for _ in range(4):
...     print(next(squares))
...
0
1
4
9

Se hai bisogno di più passaggi e la serie non è troppo lunga, chiama semplicemente list() su di essa:

 
>>> list(square_yield(4))
[0, 1, 4, 9]

Scelta geniale della parola yield perché si applicano significati :

  

rendimento - produce o fornisce (come in agricoltura)

... fornire i prossimi dati della serie.

  

rendimento - cedere o abbandonare (come in potere politico)

... rinuncia all'esecuzione della CPU fino all'avanzamento dell'iteratore.

    
174
2019-01-04 15: 30: 21Z

Resa ti dà un generatore.

 
def get_odd_numbers(i):
    return range(1, i, 2)
def yield_odd_numbers(i):
    for x in range(1, i, 2):
       yield x
foo = get_odd_numbers(10)
bar = yield_odd_numbers(10)
foo
[1, 3, 5, 7, 9]
bar
<generator object yield_odd_numbers at 0x1029c6f50>
bar.next()
1
bar.next()
3
bar.next()
5

Come puoi vedere, nel primo caso foo conserva l'intera lista in memoria in una sola volta. Non è un grosso problema per una lista con 5 elementi, ma cosa succede se si desidera un elenco di 5 milioni? Non solo è un enorme divoratore di memoria, ma richiede anche molto tempo per essere costruito nel momento in cui viene chiamata la funzione.

Nel secondo caso, bar ti dà solo un generatore. Un generatore è un iterabile, il che significa che è possibile utilizzarlo in un ciclo for, ecc., Ma è possibile accedere a ciascun valore solo una volta. Tutti i valori non vengono anche memorizzati nella memoria contemporaneamente; l'oggetto generatore "ricorda" dove si trovava nel ciclo l'ultima volta che l'hai chiamato - in questo modo, se usi un iterabile per (dire) conta fino a 50 miliardi, non devi contare fino a 50 miliardi tutti subito e memorizza i 50 miliardi di numeri da contare.

Ancora una volta, questo è un esempio abbastanza ingegnoso, probabilmente useresti itertools se davvero volessi contare su 50 miliardi. :)

Questo è il caso d'uso più semplice dei generatori. Come hai detto, può essere usato per scrivere permute efficienti, usando yield per spingere le cose attraverso lo stack di chiamate invece di usare una sorta di variabile stack. I generatori possono anche essere usati per l'attraversamento di alberi specializzato e ogni sorta di altre cose.

    
160
2019-03-13 06: 04: 08Z
  1. Solo una nota - in Python 3, range restituisce anche un generatore invece di una lista, quindi vedresti anche un'idea simile, tranne che __repr__/__str__ sono sovrascritti per mostrare un risultato migliore, in questo caso range(1, 10, 2).
    2019-03-21 18: 33: 13Z

Restituisce un generatore. Non ho molta familiarità con Python, ma credo che sia lo stesso tipo di blocchi iteratori di C # se hai familiarità con thoSE.

L'idea chiave è che il compilatore /interprete /qualsiasi cosa faccia qualche trucco in modo tale che per quanto riguarda il chiamante, possono continuare a chiamare next () e manterrà valori di ritorno - come se il metodo del generatore fosse in pausa . Ora ovviamente non si può realmente "mettere in pausa" un metodo, quindi il compilatore costruisce una macchina a stati per ricordare dove si è attualmente e quali sono le variabili locali ecc. Questo è molto più semplice della scrittura di un iteratore.

    
153
2018-10-31 08: 42: 59Z

Esiste un tipo di risposta che non mi sento ancora stato dato, tra le molte grandi risposte che descrivono come utilizzare i generatori. Ecco la risposta alla teoria del linguaggio di programmazione:

L'istruzione yield in Python restituisce un generatore. Un generatore in Python è una funzione che restituisce continuations (e in particolare un tipo di coroutine, ma le continuazioni rappresentano il meccanismo più generale per capire cosa sta succedendo).

Le continuazioni nella teoria dei linguaggi di programmazione sono un tipo di calcolo molto più fondamentale, ma non sono spesso utilizzate, perché sono estremamente difficili da ragionare e anche molto difficili da implementare. Ma l'idea di cosa sia una continuazione, è semplice: è lo stato di un calcolo che non è ancora finito. In questo stato, vengono salvati i valori correnti delle variabili, le operazioni che devono ancora essere eseguite e così via. Quindi, in un momento successivo del programma, è possibile richiamare la continuazione, in modo tale che le variabili del programma vengano reimpostate su quello stato e le operazioni che sono state salvate vengano eseguite.

Le continue, in questa forma più generale, possono essere implementate in due modi. Nel modo call/cc, lo stack del programma viene letteralmente salvato e quindi quando viene richiamata la continuazione, lo stack viene ripristinato.

In continuation passing style (CPS), le continuazioni sono solo funzioni normali (solo nelle lingue in cui le funzioni sono di prima classe) che il programmatore gestisce e passa esplicitamente alle subroutine. In questo stile, lo stato del programma è rappresentato dalle chiusure (e dalle variabili che sono codificate in esse) piuttosto che dalle variabili che risiedono da qualche parte nello stack. Le funzioni che gestiscono il flusso di controllo accettano la continuazione come argomenti (in alcune varianti di CPS, le funzioni possono accettare più continuazioni) e manipolano il flusso di controllo invocandole semplicemente chiamandole e ritornando in seguito. Un esempio molto semplice di continuation passing style è il seguente:

 
def save_file(filename):
  def write_file_continuation():
    write_stuff_to_file(filename)

  check_if_file_exists_and_user_wants_to_overwrite(write_file_continuation)

In questo esempio (molto semplicistico), il programmatore salva l'operazione di scrivere effettivamente il file in una continuazione (che può essere potenzialmente un'operazione molto complessa con molti dettagli da scrivere), e quindi passa tale continuazione (cioè come una chiusura di prima classe) a un altro operatore che esegue un po 'più di elaborazione, e poi lo chiama se necessario. (Uso questo modello di progettazione molto nella programmazione GUI effettiva, sia perché mi salva le righe di codice o, soprattutto, per gestire il flusso di controllo dopo l'attivazione degli eventi della GUI.)

Il resto di questo post, senza perdita di generalità, concettualizzerà le continuazioni come CPS, perché è molto più facile da capire e leggere.

Ora parliamo di generatori in Python. I generatori sono uno specifico sottotipo di continuazione. Mentre le continuazioni sono in generale in grado di salvare lo stato di un calcolo (cioè lo stack di chiamate del programma), i generatori sono in grado di salvare lo stato di iterazione solo un iteratore . Anche se, questa definizione è leggermente fuorviante per alcuni casi d'uso di generatori. Ad esempio:

 
def f():
  while True:
    yield 4

Questo è chiaramente un iterabile ragionevole il cui comportamento è ben definito - ogni volta che il generatore lo itera sopra, restituisce 4 (e lo fa per sempre). Ma non è probabilmente il tipo prototipo di iterabile che viene in mente quando si pensa agli iteratori (cioè, for x in collection: do_something(x)). Questo esempio illustra la potenza dei generatori: se qualcosa è un iteratore, un generatore può salvare lo stato della sua iterazione.

Per ripetere: le continue possono salvare lo stato dello stack di un programma e i generatori possono salvare lo stato di iterazione. Ciò significa che le continuazioni sono molto più potenti dei generatori, ma anche che i generatori sono molto, molto più semplici. Sono più facili da implementare per il progettista di linguaggi e sono più facili da usare per il programmatore (se hai tempo da perdere, prova a leggere e capire questa pagina su continuations e call /cc ).

Ma potresti facilmenteimplementare (e concettualizzare) generatori come un caso semplice e specifico di stile di passaggio di continuazione:

Ogni volta che viene chiamato yield, indica alla funzione di restituire una continuazione. Quando la funzione viene richiamata, parte da dove era stata interrotta. Quindi, in pseudo-pseudocodice (vale a dire, non pseudocodice, ma non codice) il metodo next del generatore è fondamentalmente il seguente:

 
class Generator():
  def __init__(self,iterable,generatorfun):
    self.next_continuation = lambda:generatorfun(iterable)

  def next(self):
    value, next_continuation = self.next_continuation()
    self.next_continuation = next_continuation
    return value

dove la parola chiave yield è in realtà zucchero sintattico per la funzione di generatore reale, in pratica qualcosa come:

 
def generatorfun(iterable):
  if len(iterable) == 0:
    raise StopIteration
  else:
    return (iterable[0], lambda:generatorfun(iterable[1:]))

Ricorda che questo è solo uno pseudocodice e l'effettiva implementazione dei generatori in Python è più complessa. Ma come esercizio per capire cosa sta succedendo, prova a utilizzare lo stile di passaggio continuo per implementare gli oggetti del generatore senza utilizzare la parola chiave yield.

    
140
2018-05-20 10: 25: 32Z

Ecco un esempio in linguaggio semplice. Fornirò una corrispondenza tra concetti umani di alto livello e concetti Python di basso livello.

Voglio operare su una sequenza di numeri, ma non voglio disturbarmi con la creazione di quella sequenza, voglio solo concentrarmi sull'operazione che voglio fare. Quindi, faccio quanto segue:

  • Ti chiamo e ti dico che voglio una sequenza di numeri che viene prodotta in un modo specifico e ti faccio sapere qual è l'algoritmo.
    Questo passaggio corrisponde a def nella funzione generatore, ad esempio la funzione contenente un yield.
  • Qualche tempo dopo, ti dico, "Ok, preparati a dirmi la sequenza dei numeri".
    Questo passaggio corrisponde alla chiamata alla funzione generatore che restituisce un oggetto generatore. Notare che non mi si dicono ancora numeri; prendi semplicemente la carta e la matita.
  • Ti chiedo, "dimmi il prossimo numero", e tu mi dici il primo numero; dopo di ciò, mi aspetti di chiederti il ​​numero successivo. È tuo compito ricordare dove ti trovavi, quali numeri hai già detto e qual è il prossimo numero. Non mi importa dei dettagli.
    Questo passaggio corrisponde alla chiamata .next() sull'oggetto generatore.
  • ... ripeti il ​​passaggio precedente, fino a ...
  • alla fine, potresti finire. Non mi dici un numero; tu solo gridi, "tieni i tuoi cavalli! Ho finito! Basta numeri!"
    Questo passaggio corrisponde all'oggetto generatore che termina il suo lavoro e genera un'eccezione StopIteration La funzione generatore non ha bisogno di aumentare l'eccezione. Viene sollevato automaticamente quando la funzione termina o emette un return.

Questo è ciò che fa un generatore (una funzione che contiene un yield); inizia l'esecuzione, si interrompe ogni volta che fa un yield e quando viene richiesto un valore .next() continua dal punto in cui era l'ultimo. Si adatta perfettamente alla progettazione con il protocollo iteratore di Python, che descrive come richiedere i valori in modo sequenziale.

L'utente più famoso del protocollo iteratore è il comando for in Python. Quindi, ogni volta che fai un:

 
for item in sequence:

non importa se sequence è una lista, una stringa, un dizionario o un generatore oggetto come descritto sopra; il risultato è lo stesso: leggi gli elementi da una sequenza uno per uno.

Si noti che la funzione def che contiene una parola chiave yield non è l'unico modo per creare un generatore; è solo il modo più semplice per crearne uno.

Per informazioni più accurate, leggi tipi di iteratore , il dichiarazione di rendimento e generatori nella documentazione di Python.

    
126
2018-05-20 10: 06: 05Z

Mentre molte risposte mostrano il motivo per cui dovresti usare un yield per creare un generatore, ci sono più usi per yield. È abbastanza facile creare una coroutine, che consente il passaggio di informazioni tra due blocchi di codice. Non ripeterò nessuno dei buoni esempi che sono già stati dati sull'utilizzo di yield per creare un generatore.

Per aiutare a capire che cosa fa un yield nel seguente codice, puoi usare il dito per tracciare il ciclo attraverso qualsiasi codice che abbia un yield. Ogni volta che il tuo dito colpisce il yield, devi aspettare che venga inserito un next o un send. Quando viene chiamato un next, tutracciare il codice fino a quando non si preme il yield ... il codice sulla destra del yield viene valutato e restituito al chiamante ... quindi si attende. Quando viene chiamato nuovamente il next, si esegue un altro ciclo attraverso il codice. Tuttavia, noterete che in una coroutine, yield può anche essere usato con un send ... che invierà un valore dal chiamante a la funzione yield. Se viene fornito un send, yield riceve il valore inviato e lo sputa dal lato sinistro ... quindi la traccia attraverso il codice avanza fino a quando non si preme nuovamente il yield (restituendo il valore alla fine, come se fosse stato chiamato next).

Ad esempio:

 
>>> def coroutine():
...     i = -1
...     while True:
...         i += 1
...         val = (yield i)
...         print("Received %s" % val)
...
>>> sequence = coroutine()
>>> sequence.next()
0
>>> sequence.next()
Received None
1
>>> sequence.send('hello')
Received hello
2
>>> sequence.close()
    
110
2014-02-04 02: 27: 35Z
  1. Carino! Un trampolino (nel senso Lisp). Non spesso si vedono quelli!
    2015-12-04 18: 31: 17Z

C'è un altro uso e significato yield (da Python 3.3):

 
yield from <expr>

Da PEP 380 - Sintassi per la delega a un subgeneratore :

  

Si propone una sintassi per un generatore per delegare parte delle sue operazioni a un altro generatore. Ciò consente di scomporre una parte del codice contenente 'yield' e di collocarla in un altro generatore. Inoltre, il subgeneratore può tornare con un valore e il valore viene reso disponibile al generatore delegante.

     

La nuova sintassi apre anche alcune opportunità di ottimizzazione quando un generatore restituisce valori prodotti da un altro.

Inoltre questo introdurrà (da Python 3.5):

 
async def new_coroutine(data):
   ...
   await blocking_action()

per evitare che le coroutine vengano confuse con un generatore regolare (oggi il yield è usato in entrambi).

    
102
2018-05-20 10: 34: 03Z

Tutte ottime risposte, comunque un po 'difficile per i neofiti.

Suppongo che tu abbia imparato l'istruzione return.

Come un'analogia, return e yield sono gemelli. return significa "ritorno e stop" mentre "rendimento" significa "ritorno, ma continua"

  
  1. Prova a ottenere una lista num con return.
  2.   
 
def num_list(n):
    for i in range(n):
        return i

Esegui:

 
In [5]: num_list(3)
Out[5]: 0

Vedi, ottieni solo un singolo numero piuttosto che un elenco di essi. return non ti consente mai di prevalere felicemente, implementa solo una volta ed esce.

  
  1. Arriva yield
  2.   

Sostituisci return con yield:

 
In [10]: def num_list(n):
    ...:     for i in range(n):
    ...:         yield i
    ...:

In [11]: num_list(3)
Out[11]: <generator object num_list at 0x10327c990>

In [12]: list(num_list(3))
Out[12]: [0, 1, 2]

Ora vinci per ottenere tutti i numeri.

Se paragoni al return che viene eseguito una volta e si arresta, yield esegue le volte che hai pianificato. È possibile interpretare return come return one of them e yield come return all of them. Questo è chiamato iterable.

  
  1. Un altro passaggio è possibile riscrivere l'istruzione yield con return
  2.   
 
In [15]: def num_list(n):
    ...:     result = []
    ...:     for i in range(n):
    ...:         result.append(i)
    ...:     return result

In [16]: num_list(3)
Out[16]: [0, 1, 2]

È il nucleo di yield.

La differenza tra un elenco di output return e l'output dell'oggetto yield è:

Otterrai sempre [0, 1, 2] da un oggetto elenco ma solo potrai recuperarli da "l'oggetto yield output" una volta. Quindi, ha un nuovo nome generator oggetto come mostrato in Out[11]: <generator object num_list at 0x10327c990>.

In conclusione, come metafora per ingannarlo:

  •  return e yield sono gemelli
  •  list e generator sono gemelli
88
2018-05-28 09: 06: 22Z
  1. Questo è comprensibile, ma una delle principali differenze è che è possibile avere più rendimenti in una funzione /metodo. L'analogia si rompe completamente a quel punto. Resa ricorda la sua posizione in una funzione, quindi la prossima volta che chiami next (), la tua funzione continua fino al prossimo yield. Questo è importante, penso, e dovrebbe essere espresso.
    2018-08-23 13: 27: 21Z

Ecco alcuni esempi di Python su come implementare effettivamente i generatori come se Python non fornisse loro zucchero sintattico:

Come generatore Python:

 
from itertools import islice

def fib_gen():
    a, b = 1, 1
    while True:
        yield a
        a, b = b, a + b

assert [1, 1, 2, 3, 5] == list(islice(fib_gen(), 5))

Utilizzo di chiusure lessicali anziché generatori

 
def ftake(fnext, last):
    return [fnext() for _ in xrange(last)]

def fib_gen2():
    #funky scope due to python2.x workaround
    #for python 3.x use nonlocal
    def _():
        _.a, _.b = _.b, _.a + _.b
        return _.a
    _.a, _.b = 0, 1
    return _

assert [1,1,2,3,5] == ftake(fib_gen2(), 5)

Utilizzo di chiusure di oggetti anziché generatori (perché ClosuresAndObjectsAreEquivalent )  

class fib_gen3:
    def __init__(self):
        self.a, self.b = 1, 1

    def __call__(self):
        r = self.a
        self.a, self.b = self.b, self.a + self.b
        return r

assert [1,1,2,3,5] == ftake(fib_gen3(), 5)
    
86
2017-10-24 10: 46: 05Z

Stavo per postare "leggi la pagina 19 di" Python: Essential Reference "di Beazley per una rapida descrizione dei generatori", ma molti altri hanno già inserito buone descrizioni.

Inoltre, si noti che il yield può essere usato nelle coroutine come il doppio del loro uso nelle funzioni del generatore. Sebbene non sia lo stesso uso dello snippet di codice, (yield) può essere usato come espressione in una funzione. Quando un chiamante invia un valore al metodo utilizzando il metodo send(), la coroutine verrà eseguita fino a quando non viene rilevata la successiva istruzione (yield).

I generatori e le coroutine sono un ottimo modo per configurare applicazioni di tipo flusso di dati. Ho pensato che valesse la pena conoscere l'altro uso dell'istruzione yield nelle funzioni.

    
82
2013-01-28 01: 37: 10Z

Da un punto di vista della programmazione, gli iteratori sono implementati come thunk .

Per implementare iteratori, generatori e pool di thread per l'esecuzione simultanea, ecc. come thunk (chiamati anche funzioni anonime), uno usa i messaggi inviati a un oggetto di chiusura, che ha un dispatcher, e il dispatcher risponde ai "messaggi".

http://en.wikipedia.org/wiki/Message_passing

" next " è un messaggio inviato a una chiusura, creato dalla chiamata " iter ".

Ci sono molti modi per implementare questo calcolo. Ho usato la mutazione, ma è facile farlo senza mutazioni, restituendo il valore corrente e il successivo yielder.

Ecco una dimostrazione che utilizza la struttura di R6RS, ma la semantica è assolutamente identica a quella di Python. È lo stesso modello di computazione, e solo una modifica della sintassi è necessaria per riscriverlo in Python.

 
Welcome to Racket v6.5.0.3.

-> (define gen
     (lambda (l)
       (define yield
         (lambda ()
           (if (null? l)
               'END
               (let ((v (car l)))
                 (set! l (cdr l))
                 v))))
       (lambda(m)
         (case m
           ('yield (yield))
           ('init  (lambda (data)
                     (set! l data)
                     'OK))))))
-> (define stream (gen '(1 2 3)))
-> (stream 'yield)
1
-> (stream 'yield)
2
-> (stream 'yield)
3
-> (stream 'yield)
'END
-> ((stream 'init) '(a b))
'OK
-> (stream 'yield)
'a
-> (stream 'yield)
'b
-> (stream 'yield)
'END
-> (stream 'yield)
'END
->
    
80
2018-05-20 10: 29: 34Z

Ecco un semplice esempio:

 
def isPrimeNumber(n):
    print "isPrimeNumber({}) call".format(n)
    if n==1:
        return False
    for x in range(2,n):
        if n % x == 0:
            return False
    return True

def primes (n=1):
    while(True):
        print "loop step ---------------- {}".format(n)
        if isPrimeNumber(n): yield n
        n += 1

for n in primes():
    if n> 10:break
    print "wiriting result {}".format(n)

Output:

 
loop step ---------------- 1
isPrimeNumber(1) call
loop step ---------------- 2
isPrimeNumber(2) call
loop step ---------------- 3
isPrimeNumber(3) call
wiriting result 3
loop step ---------------- 4
isPrimeNumber(4) call
loop step ---------------- 5
isPrimeNumber(5) call
wiriting result 5
loop step ---------------- 6
isPrimeNumber(6) call
loop step ---------------- 7
isPrimeNumber(7) call
wiriting result 7
loop step ---------------- 8
isPrimeNumber(8) call
loop step ---------------- 9
isPrimeNumber(9) call
loop step ---------------- 10
isPrimeNumber(10) call
loop step ---------------- 11
isPrimeNumber(11) call

Non sono uno sviluppatore Python, ma mi sembra che yield mantenga la posizione del flusso del programma e il prossimo ciclo inizi dalla posizione "yield". Sembra che sia in attesa di quella posizione e, poco prima, restituisce un valore all'esterno e la prossima volta continua a funzionare.

Sembra un'abilità interessante e carina: D

    
72
2018-05-20 10: 31: 01Z
  1. Non dimenticare che 2 è primo: -)
    2018-07-02 01: 20: 23Z
  2. Hai ragione. Ma qual è l'effetto sul flusso che è vedere il comportamento del "rendimento"? Posso cambiare l'algoritmo nel nome della matematica. Aiuterà a ottenere una diversa valutazione del "rendimento"?
    2018-07-02 01: 44: 08Z

Ecco un'immagine mentale di ciò che fa yield.

Mi piace pensare che un thread abbia uno stack (anche quando non è implementato in quel modo).

Quando viene chiamata una funzione normale, mette le sue variabili locali nello stack, esegue alcuni calcoli, quindi cancella lo stack e ritorna. I valori delle sue variabili locali non vengono mai più visti.

Con una funzione yield, quando il suo codice inizia a funzionare (vale a dire dopo che la funzione è stata chiamata, restituendo un oggetto generatore, il cui metodo next() viene quindi invocato), mette le sue variabili locali nello stack e calcola per un po '. Ma poi, quando colpisce l'istruzione yield, prima di cancellare la sua parte dello stack e tornare, prende uno snapshot delle sue variabili locali e le memorizza nell'oggetto generatore. Scrive anche il punto in cui si trova attualmente nel suo codice (ad esempio la particolare dichiarazione yield).

Quindi è una specie di funzione congelata su cui è sospeso il generatore.

Quando il next() viene chiamato successivamente, recupera gli oggetti della funzione sullo stack e lo anima nuovamente. La funzione continua a calcolare da dove era stata interrotta, ignaro del fatto che aveva appena passato un'eternità in un magazzino freddo.

Confronta i seguenti esempi:

 
def normalFunction():
    return
    if False:
        pass

def yielderFunction():
    return
    if False:
        yield 12

Quando chiamiamo la seconda funzione, si comporta in modo molto diverso dalla prima. L'istruzione yield potrebbe essere irraggiungibile, ma se è presente ovunque, cambia la natura di ciò con cui abbiamo a che fare.

 
>>> yielderFunction()
<generator object yielderFunction at 0x07742D28>

Chiamando yielderFunction() non si esegue il suo codice, ma si crea un generatore fuori dal codice. (Forse è una buona idea denominare tali cose con il prefisso yielder per la leggibilità.)

 
>>> gen = yielderFunction()
>>> dir(gen)
['__class__',
 ...
 '__iter__',    #Returns gen itself, to make it work uniformly with containers
 ...            #when given to a for loop. (Containers return an iterator instead.)
 'close',
 'gi_code',
 'gi_frame',
 'gi_running',
 'next',        #The method that runs the function's body.
 'send',
 'throw']

I campi gi_code e gi_frame sono dove viene memorizzato lo stato congelato. Esplorandoli con dir(..), possiamo confermare che il nostro modello mentale sopra è credibile.

    
59
2017-03-01 13: 36: 58Z

Come ogni risposta suggerisce, yield viene usato per creare un generatore di sequenze. È usato per generare alcune sequenze in modo dinamico. Ad esempio, durante la lettura di un file riga per riga su una rete, è possibile utilizzare la funzione yield come segue:

 
def getNextLines():
   while con.isOpen():
       yield con.read()

Puoi usarlo nel tuo codice come segue:

 
for line in getNextLines():
    doSomeThing(line)

Trasferimento controllo esecuzione ottenuto

Il controllo di esecuzione verrà trasferito da getNextLines () al ciclo for quando viene eseguito il rendimento. Pertanto, ogni volta che viene richiamato getNextLines (), l'esecuzione inizia dal punto in cui è stata messa in pausa l'ultima volta.

Quindi, in breve, una funzione con il seguente codice

 
def simpleYield():
    yield "first time"
    yield "second time"
    yield "third time"
    yield "Now some useful value {}".format(12)

for i in simpleYield():
    print i

stamperà

 
"first time"
"second time"
"third time"
"Now some useful value 12"
    
50
2018-05-20 10: 42: 59Z

Il rendimento è un oggetto

Un return in una funzione restituirà un singolo valore.

Se vuoi una funzione per restituire un enorme insieme di valori , usa yield.

Ancora più importante, yield è una barriera .

  

come barriera nel linguaggio CUDA, non trasferirà il controllo fino a quando non lo ottiene   completata.

Cioè, eseguirà il codice nella tua funzione dall'inizio fino a quando non raggiunge yield. Quindi, restituirà il primo valore del ciclo.

Quindi, ogni altra chiamata eseguirà il ciclo che hai scritto nella funzione ancora una volta, restituendo il valore successivo fino a quando non c'è alcun valore da restituire.

    
44
2018-05-20 10: 45: 50Z

(La mia risposta qui sotto parla solo dal punto di vista dell'uso del generatore Python, non del implementazione sottostante del meccanismo generatore , che implica alcuni trucchi di manipolazione dello stack e dell'heap.)

Quando viene usato il yield invece di un return in una funzione python, quella funzione diventa qualcosa di speciale chiamato generator function. Quella funzione restituirà un oggetto di tipo generator. La parola chiave yield è un flag che notifica al compilatore python di trattare tale funzione in modo speciale. Le normali funzioni terminano quando viene restituito un valore da esso. Ma con l'aiuto del compilatore, la funzione generatore può essere pensata ​​strong> come riassumibile. Cioè, il contesto di esecuzione verrà ripristinato e l'esecuzione continuerà dall'ultima esecuzione. Fino a terichiama esplicitamente return, che solleverà un'eccezione StopIteration (che è anche parte del protocollo iteratore) o raggiungerà la fine della funzione. Ho trovato molti riferimenti su generator ma questo uno del functional programming perspective è il più digeribile.

(Ora voglio parlare della logica alla base del generator e del iterator sulla base della mia comprensione. Spero che questo possa aiutarti a cogliere la motivazione essenziale dell'iteratore e generatore.Questo concetto si presenta anche in altre lingue come C #.)

Come capisco, quando vogliamo elaborare una serie di dati, di solito prima salviamo i dati da qualche parte e poi li elaboriamo uno per uno. Ma questo approccio ingenuo è problematico. Se il volume dei dati è enorme, è costoso archiviarli nel loro insieme in anticipo. Quindi, invece di memorizzare direttamente lo stesso data, perché non memorizzare una specie di metadata indirettamente, cioè the logic how the data is computed .

Ci sono 2 approcci per avvolgere tali metadati.

  1. L'approccio OO, avvolgiamo i metadati as a class. Questo è il cosiddetto iterator che implementa il protocollo iteratore (cioè i metodi __next__() e __iter__()). Questo è anche il
  2. comunemente .
  3. L'approccio funzionale, avvolgiamo i metadati as a function. Questo è il cosiddetto generator function. Ma sotto il cofano, il generator object restituito è ancora IS-A iteratore perché implementa anche il protocollo iteratore.

In ogni caso, viene creato un iteratore, ad esempio un oggetto che può darti i dati che desideri. L'approccio OO potrebbe essere un po 'complesso. Comunque, quale usare dipende da te.

    
44
2018-11-23 01: 38: 59Z

In breve, l'istruzione yield trasforma la tua funzione in una fabbrica che produce un oggetto speciale chiamato generator che avvolge il corpo della tua funzione originale. Quando il generator viene iterato, esegue la funzione finché non raggiunge il successivo yield, quindi sospende l'esecuzione e valuta il valore passato a yield. Ripete questo processo su ogni iterazione finché il percorso dell'esecuzione non esce dalla funzione. Ad esempio,

 
def simple_generator():
    yield 'one'
    yield 'two'
    yield 'three'

for i in simple_generator():
    print i

mostra semplicemente

 
one
two
three

L'energia viene dall'utilizzo del generatore con un ciclo che calcola una sequenza, il generatore esegue il ciclo arrestandosi ogni volta per "cedere" al risultato successivo del calcolo, in questo modo calcola una lista al volo, il vantaggio essendo la memoria salvata per calcoli particolarmente grandi

Supponiamo che tu voglia creare una tua funzione range che produca un intervallo iterabile di numeri, potresti farlo in questo modo,

 
def myRangeNaive(i):
    n = 0
    range = []
    while n < i:
        range.append(n)
        n = n + 1
    return range

e usalo in questo modo;

 
for i in myRangeNaive(10):
    print i

Ma questo è inefficiente perché

  • Crei un array che usi una volta sola (spreca memoria)
  • Questo codice scorre in realtà su quell'array due volte! : (

Fortunatamente Guido e il suo team sono stati abbastanza generosi da sviluppare i generatori in modo da poterlo fare,

 
def myRangeSmart(i):
    n = 0
    while n < i:
       yield n
       n = n + 1
    return

for i in myRangeSmart(10):
    print i

Ora su ogni iterazione una funzione sul generatore chiamato next() esegue la funzione finché non raggiunge un'istruzione 'yield' in cui si arresta e 'restituisce' il valore o raggiunge la fine della funzione. In questo caso alla prima chiamata, next() esegue l'istruzione yield e produce 'n', alla prossima chiamata eseguirà l'istruzione incrementale, tornerà al 'while', la valuterà, e se è vera, si fermerà e restituisce 'n' di nuovo, continuerà in questo modo finché la condizione while non restituisce false e il generatore salta alla fine della funzione.

    
43
2018-05-20 11: 04: 36Z

Molte persone usano return anziché yield, ma in alcuni casi yield può essere più efficiente e più facile da lavorare.

Ecco un esempio che yield è sicuramente il migliore per:

  

ritorno (in funzione)

 
import random

def return_dates():
    dates = [] # With 'return' you need to create a list then return it
    for i in range(5):
        date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
        dates.append(date)
    return dates
  

rendimento (in funzione)

 
def yield_dates():
    for i in range(5):
        date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
        yield date # 'yield' makes a generator automatically which works
                   # in a similar way. This is much more efficient.
  

Funzioni di chiamata ​​strong>

 
dates_list = return_dates()
print(dates_list)
for i in dates_list:
    print(i)

dates_generator = yield_dates()
print(dates_generator)
for i in dates_generator:
    print(i)

Entrambe le funzioni fanno la stessa cosa, ma yield usa tre linee anziché cinque e ha una variabile in meno di cui preoccuparsi.

  
    

Questo è il risultato del code:

  

 Output

Come puoi vedere entrambe le funzioni fanno la stessa cosa. L'unica differenza è che return_dates() fornisce una lista e yield_dates() fornisce un generatore.

Un esempio di vita reale potrebbe essere qualcosa come leggere un file riga per riga o se vuoi solo creare un generatore.

    
41
2018-05-20 11: 02: 52Z

yield è come un elemento di ritorno per una funzione. La differenza è che l'elemento yield trasforma una funzione in un generatore. Un generatore si comporta come una funzione finché qualcosa non viene "ceduto". Il generatore si arresta finché non viene richiamato e continua esattamente dallo stesso punto in cui è stato avviato. Puoi ottenere una sequenza di tutti i valori "ottenuti" in uno, chiamando list(generator()).

    
36
2015-05-20 06: 19: 32Z

La parola chiave yield raccoglie semplicemente i risultati di ritorno. Pensa a yield come return +=

    
35
2016-04-23 03: 16: 19Z

Ecco un semplice approccio basato su yield, per calcolare la serie di fibonacci, spiegato:

 
def fib(limit=50):
    a, b = 0, 1
    for i in range(limit):
       yield b
       a, b = b, a+b

Quando inserisci questo nel tuo REPL e poi prova a chiamarlo, otterrai un risultato mistificante:

 
>>> fib()
<generator object fib at 0x7fa38394e3b8>

Questo perché la presenza di yield ha segnalato a Python che si desidera creare un generatore , ovvero un oggetto che genera valori su richiesta.

Quindi, come si generano questi valori? Questo può essere fatto direttamente usando la funzione built-in next o, indirettamente, alimentandolo con un costrutto che consuma valori.

Usando la funzione next() integrata, invochi direttamente .next/__next__, forzando il generatore a produrre un valore:

 
>>> g = fib()
>>> next(g)
1
>>> next(g)
1
>>> next(g)
2
>>> next(g)
3
>>> next(g)
5

Indirettamente, se fornisci il fib a un loop for, un inizializzatore list, un inizializzatore tuple o qualsiasi altra cosa che si aspetta un oggetto che genera /produce valori, "consumerai" il generatore fino a quando non saranno più prodotti valori da esso (e restituisce):

 
results = []
for i in fib(30):       # consumes fib
    results.append(i) 
# can also be accomplished with
results = list(fib(30)) # consumes fib

Allo stesso modo, con un inizializzatore tuple:

 
>>> tuple(fib(5))       # consumes fib
(1, 1, 2, 3, 5)

Un generatore si differenzia da una funzione nel senso che è pigro. Ciò si ottiene mantenendo il suo stato locale e consentendoti di riprenderti quando necessario.

Quando invochi per la prima volta il fib chiamandolo:

 
f = fib()

Python compila la funzione, incontra la parola chiave yield e restituisce semplicemente un oggetto generatore. Non molto utile sembra.

Quando lo richiedi, genera il primo valore, direttamente o indirettamente, esegue tutte le istruzioni che trova, finché non incontra un yield, quindi restituisce il valore che hai fornito a yield e fa una pausa. Per un esempio che dimostra meglio questo, usiamo alcune chiamate print (sostituisci con print "text" se su Python 2):

 
def yielder(value):
    """ This is an infinite generator. Only use next on it """ 
    while 1:
        print("I'm going to generate the value for you")
        print("Then I'll pause for a while")
        yield value
        print("Let's go through it again.")

Ora, inserisci nella REPL:

 
>>> gen = yielder("Hello, yield!")

hai un oggetto generatore in attesa di un comando per generare un valore. Usa next e vedi cosa viene stampato:

 
>>> next(gen) # runs until it finds a yield
I'm going to generate the value for you
Then I'll pause for a while
'Hello, yield!'

I risultati non quotati sono ciò che viene stampato. Il risultato quotato è ciò che viene restituito da yield. Chiama ancora il next:

 
>>> next(gen) # continues from yield and runs again
Let's go through it again.
I'm going to generate the value for you
Then I'll pause for a while
'Hello, yield!'

Il generatore ricorda che è stato messo in pausa a yield value e riprende da lì. Viene stampato il messaggio successivo e viene eseguita nuovamente la ricerca dell'istruzione yield per eseguirla nuovamente (a causa del ciclo while).

    
32
2017-07-12 12: 44: 24Z

Un semplice esempio per capire di cosa si tratta: yield

 
def f123():
    for _ in range(4):
        yield 1
        yield 2


for i in f123():
    print i

L'output è:

 
1 2 1 2 1 2 1 2
    
32
2019-06-06 12: 08: 26Z
fonte posta Qui