python generator - Cosa fa la parola chiave "rendimento"?




yield list (25)

Qual è l'uso della 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 si fermeranno le chiamate successive?

1. Il codice proviene da Jochen Schulz (jrschulz), che ha creato una grande libreria Python per gli spazi metrici. Questo è il link alla fonte completa: Modulo mspace .


Answers

Sta restituendo un generatore. Non ho molta familiarità con Python, ma credo che sia lo stesso tipo di blocco iteratore di C # se hai familiarità con quelli.

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à i valori di ritorno - come se il metodo del generatore fosse sospeso . 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ù facile che scrivere un iteratore tu stesso.


Mentre molte risposte mostrano il motivo per cui dovresti usare a yieldper 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 begli esempi che sono già stati dati sull'uso yieldper creare un generatore.

Per aiutare a capire cosa yieldfa nel codice seguente, puoi usare il dito per tracciare il ciclo attraverso qualsiasi codice che abbia un yield. Ogni volta che il dito colpisce il yield, bisogna aspettare per una nexto sendda inserire. Quando nextviene chiamato un, si traccia attraverso il codice fino a quando non si preme il yield... il codice a destra di yieldviene valutato e restituito al chiamante ... quindi si attende. Quando nextviene richiamato, si esegue un altro ciclo attraverso il codice. Tuttavia, noterete che in una coroutine, yieldpuò anche essere usato con un send... che invierà un valore dal chiamante nella funzione di resa. Se a sendviene dato, allorayieldriceve il valore inviato e lo sputa dal lato sinistro ... quindi la traccia attraverso il codice progredisce fino a quando non si preme di yieldnuovo (restituendo il valore alla fine, come se nextfosse stato chiamato).

Per 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()

Cosa fa la parola chiave yield in Python?

Risposta Struttura / Riepilogo

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

generatori:

yield è legale solo all'interno di una definizione di funzione e l'inclusione del 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 è frozen al 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 per implementare il protocollo iteratore , definito dai seguenti due metodi: __iter__ e next (Python 2) o __next__ (Python 3). Entrambi questi metodi rendono un oggetto un iteratore che è possibile digitare-verificare con la classe base astratta Iterator dal modulo delle 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 check-in in questo modo:

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

Una caratteristica di un Iterator è che una volta esaurito , non è possibile riutilizzarlo o resettarlo:

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

Dovrai fare 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, il yield from consente anche la delega ai subgeneratori, che verrà spiegato nella sezione seguente sulla delega cooperativa con sub-coroutine.

coroutine:

yield rappresenta un'espressione che consente di inviare dati al generatore (vedi nota 3)

Ecco un esempio, prendi nota della variabile received , che punterà ai dati che vengono 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 . __next__ metodo next o __next__ appropriato, a seconda della versione di Python che si sta utilizzando:

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

E ora possiamo inviare dati al generatore. (L' invio di None equivale a chiamare in next .):

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

Delegazione cooperativa a sub-coroutine con yield from

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

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 utilizzato da un generatore 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 precisa semantica della yield from in PEP 380.

Altri metodi: chiudere e lanciare

Il metodo close genera GeneratorExit nel momento in cui l'esecuzione della funzione è stata congelata. Questo sarà anche chiamato da __del__ modo da poter inserire qualsiasi codice di pulizia in cui __del__ GeneratorExit :

>>> my_account.close()

È inoltre possibile generare un'eccezione che può essere gestita nel generatore o propagata 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:

Cosa fa la parola chiave yield in Python?

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

Appendice:

Critica della risposta in alto / accettata **

  • È confuso su ciò che rende un iterable , usando solo un elenco come esempio. Vedi i miei riferimenti sopra, ma in sintesi: un iterabile ha un metodo __iter__ che restituisce un iteratore . Un iteratore fornisce un .next (Python 2 o .__next__ (Python 3), che viene chiamato implicitamente da cicli for fino a quando non solleva StopIteration e, una volta fatto, continuerà a farlo.
  • Quindi utilizza un'espressione di generatore per descrivere cosa è un generatore. Dato che un generatore è semplicemente un modo conveniente per creare un iteratore , confonde solo la questione, e non abbiamo ancora ottenuto la parte di yield .
  • Nel controllo dell'esaurimento di un generatore , chiama il metodo .next , quando invece dovrebbe usare la funzione integrata, di 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 la yield ha a tutti.
  • Nessuna discussione sui metodi che fornisce fornisce insieme alla nuova yield from in Python 3. La risposta superiore / accettata è una risposta molto incompleta.

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

La grammatica attualmente 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

Dal momento che la resa è un'espressione, è stata propagandata da alcuni come interessante utilizzarla nelle comprensioni o nell'espressione di generatore - nonostante non citi un caso d'uso particolarmente valido.

Gli sviluppatori principali di CPython stanno discutendo di deprecare la sua indennità . 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. Lasciare le cose come sono in Python 3 non va bene, IMHO.

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

Sono d'accordo che per noi è un posto sensato finire, poiché qualsiasi codice basato sul comportamento attuale è davvero troppo intelligente per essere mantenibile.

In termini di arrivarci, probabilmente vorremmo:

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

Saluti, Nick.

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

Inoltre, c'è un problema in sospeso (10544) che sembra indicare la direzione di questa idea non è mai una buona idea (PyPy, un'implementazione Python scritta in Python, sta già sollevando avvisi di sintassi).

In conclusione, fino a quando gli sviluppatori di CPython non ci diranno diversamente: non mettere il yield in un'espressione o comprensione di generatore.

La dichiarazione di return in un generatore

In Python 2 :

In una funzione generatore, l'istruzione return non è autorizzata a includere un expression_list . In quel contesto, un semplice return indica che il generatore è stato fatto e causerà l' StopIteration di StopIteration .

An expression_listè praticamente un numero qualsiasi 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, la returndichiarazione indica che il generatore è fatto e farà StopIterationsorgere. Il valore restituito (se presente) viene utilizzato come argomento da costruire StopIteratione diventa l' StopIteration.valueattributo.

Le 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 fornire dati intermedi su richiesta dall'utente. Questo ha promesso di essere superiore nelle prestazioni ad altri approcci, incluso il threading Python , che non è nemmeno disponibile su alcuni sistemi.

  2. Ciò significa, ad esempio, che gli xrangeoggetti ( rangein Python 3) non sono Iterators, anche se sono iterabili, perché possono essere riutilizzati. Come gli elenchi, i loro __iter__metodi restituiscono oggetti iteratore.

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


Qui c'è 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)

Produzione:

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 yieldmantenga la posizione del flusso del programma e il prossimo ciclo inizi dalla posizione "yield". Sembra che stia aspettando quella posizione, e poco prima, restituendo un valore all'esterno, e la prossima volta continui a lavorare.

Sembra essere un'abilità interessante e piacevole: D


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

Presumo che tu abbia appreso la returndichiarazione.

Come un'analogia returne yieldsono gemelli. returnsignifica 'ritorno e stop' mentre 'rendimento' significa 'ritorno, ma continua'

  1. Cerca di ottenere un num_list con return.
def num_list(n):
    for i in range(n):
        return i

Eseguirlo:

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

Vedi, ottieni solo un singolo numero piuttosto che un elenco di essi. returnnon ti consente mai di prevalere felicemente, implementa solo una volta e smetti.

  1. Arriva yield

Sostituisci returncon 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.

Confrontando con returnquali esecuzioni una volta e fermandosi, yieldesegui i tempi che hai pianificato. Puoi interpretare returncome return one of them, e yieldcome return all of them. Questo è chiamato iterable.

  1. Un altro passo possiamo riscrivere la yielddichiarazione conreturn
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 returnoutput di lista e l' yieldoutput dell'oggetto è:

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

In conclusione, come metafora per ingannarlo:

  • returne yieldsono gemelli
  • liste generatorsono gemelli

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

Per implementare iteratori, generatori e pool di thread per l'esecuzione simultanea, ecc. Come thunk (chiamati anche funzioni anonime), si utilizzano 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 mutazione, restituendo il valore corrente e il prossimo 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
->

Per coloro che preferiscono un esempio di lavoro minimo, meditate 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

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.


C'è un tipo di risposta che non mi sento ancora stato dato, tra le tante grandi risposte che descrivono come usare i generatori. Ecco la risposta della teoria del linguaggio di programmazione:

L' yieldistruzione in Python restituisce un generatore. Un generatore in Python è una funzione che restituisce continuazioni (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 una computazione che non è ancora finita. 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 tale stato e vengano eseguite le operazioni che sono state salvate.

Le continue, in questa forma più generale, possono essere implementate in due modi. Nel call/ccmodo, 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 in un punto dello 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 quella continuazione (cioè, come prima chiusura di classe) a un altro operatore che esegue un po 'più di elaborazione, e poi lo chiama se necessario. (Uso questo schema 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 su un iteratore . Anche se, questa definizione è leggermente fuorviante per alcuni casi d'uso di generatori. Per 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 il potere 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 per masterizzare, prova a leggere e comprendere questa pagina sui continuations e call / cc ).

Ma potresti facilmente implementare (e concettualizzare) i generatori come un caso semplice e specifico di stile di passaggio continuo:

Ogni volta che yieldviene chiamato, dice alla funzione di restituire una continuazione. Quando la funzione viene richiamata, parte da dove era stata interrotta. Quindi, in pseudo-pseudocodice (cioè, non pseudocodice, ma non codice) il nextmetodo 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 yieldparola chiave è in realtà zucchero sintattico per la funzione 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 oggetti generatori senza utilizzare la yieldparola chiave.


Ecco un yieldapproccio semplice basato, per calcolare la serie di fibonacci, ha 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 yieldsegnali a Python che si desidera creare un generatore , vale a dire 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 built-in next(), 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 fibun forciclo, un listinizializzatore, un tupleinizializzatore o qualsiasi altra cosa che si aspetta un oggetto che genera / produce valori, "consumerai" il generatore fino a quando non sarà più prodotto 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 tupleinizializzatore:

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

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

Quando invochi fibper la prima volta chiamandolo:

f = fib()

Python compila la funzione, incontra la yieldparola chiave e restituisce semplicemente un oggetto generatore a te. 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 fornito yielde sospeso. Per un esempio che dimostra meglio questo, usiamo alcune printchiamate (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 ora in attesa di un comando per generare un valore. Usa nexte 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 indicato è ciò che viene restituito yield. Chiama di nextnuovo ora:

>>> 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 yield valuee riprende da lì. Il prossimo messaggio viene stampato e la ricerca yielddell'istruzione da sospendere viene eseguita di nuovo (a causa del whileciclo).


La yieldparola chiave raccoglie semplicemente i risultati di ritorno. Pensa a yieldcomereturn +=


Pensare 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 di cui sopra:

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. :)


TL; DR

Invece di questo:

def squares_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 squares_the_yield_way(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, yieldogni pezzo invece.

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

yield è un modo zuccheroso per dire

costruisci una serie di cose

Stesso comportamento:

>>> for square in squares_list(4):
...     print(square)
...
0
1
4
9
>>> for square in squares_the_yield_way(4):
...     print(square)
...
0
1
4
9

Comportamento diverso:

Il rendimento è single-pass : puoi solo ripetere una volta. Quando una funzione ha un rendimento, la chiamiamo funzione generatore . E un iterator è ciò che restituisce. Questo è rivelatore. Perdiamo la comodità di un contenitore, ma otteniamo il potere di una serie arbitrariamente lunga.

Il rendimento è pigro , mette fuori calcolo. Una funzione con un rendimento in esso in realtà non viene eseguita affatto quando la chiami. L'oggetto iteratore che restituisce utilizza la magic per mantenere il contesto interno della funzione. Ogni volta che chiamate next()l'iteratore (questo accade in un ciclo for), i pollici dell'esecuzione avanzano al rendimento successivo. ( returnsolleva StopIteratione termina la serie.)

Il rendimento è versatile . Può fare loop infiniti:

>>> 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ù pass e la serie non è troppo lunga, basta chiamarci list():

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

Scelta geniale della parola yieldperché si applicano entrambi i significati :

rendimento - produrre o fornire (come in agricoltura)

... fornire i prossimi dati della serie.

rendimento - cedere o rinunciare (come nel potere politico)

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


Come ogni risposta suggerisce, yieldè usata 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 yieldfunzione come segue:

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

Puoi usarlo nel tuo codice come segue:

for line in getNextLines():
    doSomeThing(line)

Gotcha di controllo dell'esecuzione

Il controllo di esecuzione verrà trasferito da getNextLines () al forciclo 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"

yieldè come un elemento di ritorno per una funzione. La differenza è che l' yieldelemento 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()).


Ecco un'immagine mentale di ciò che yieldfa.

Mi piace pensare a un thread come a uno stack (anche se non è implementato in questo 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 yieldfunzione, quando il suo codice inizia a funzionare (cioè dopo che la funzione è stata chiamata, restituendo un oggetto generatore, il cui next()metodo è quindi invocato), mette le sue variabili locali nello stack e calcola per un po '. Ma poi, quando colpisce la yielddichiarazione, 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 è attualmente nel suo codice (cioè la yielddichiarazione particolare ).

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

Quando 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 una cella frigorifera.

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 dal primo. L' yieldaffermazione potrebbe essere irraggiungibile, ma se è presente ovunque, cambia la natura di ciò con cui abbiamo a che fare.

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

La chiamata yielderFunction()non esegue il suo codice, ma crea un generatore fuori dal codice. (Forse è una buona idea denominare tali cose con il yielderprefisso 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_codee gi_framesono dove viene memorizzato lo stato congelato. Esplorandoli con dir(..), possiamo confermare che il nostro modello mentale sopra è credibile.


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

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

list.extendchiama 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.


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

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 all'integrazione defdella funzione generatore, ovvero la funzione contenente a yield.
  • Qualche tempo dopo, ti dico, "Ok, preparati a dirmi la sequenza dei numeri".
    Questo passaggio corrisponde a chiamare la funzione generatore che restituisce un oggetto generatore. Nota che non mi dici ancora nessun numero; ti basta prendere 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. È compito tuo 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.
  • ... ripetere 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 StopIterationun'eccezione La funzione generatore non ha bisogno di aumentare l'eccezione. Viene sollevato automaticamente quando la funzione termina o genera a return.

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

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

for item in sequence:

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

Nota che defentrare in una funzione che contiene una yieldparola chiave non è l'unico modo per creare un generatore; è solo il modo più semplice per crearne uno.

Per informazioni più accurate, leggere i tipi di iteratore , la dichiarazione di rendimento e i generators nella documentazione di Python.


Ancora un altro TL; DR

Iterator on list : next()restituisce il prossimo elemento della lista

Generatore di Iterator : next()calcolerà il prossimo elemento al volo (esegui il codice)

Puoi vedere il rendimento / generatore come un modo per eseguire manualmente il flusso di controllo dall'esterno (come il ciclo continuo di un passo), chiamando next, per quanto complesso il flusso.

Nota : il generatore NON è una funzione normale. Ricorda lo stato precedente come le variabili locali (stack). Vedi altre risposte o articoli per una spiegazione dettagliata. Il generatore può essere iterato solo una volta . Potresti fare a meno yield, ma non sarebbe così bello, quindi può essere considerato zucchero di lingua "molto carino".


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

yield from <expr>

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

Una sintassi viene proposta per un generatore per delegare parte delle sue operazioni a un altro generatore. Ciò consente di scomporre una parte di codice contenente "yield" e di inserirla 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 this introdurrà (dal momento che Python 3.5):

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

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


Collegamento al yield Grokking

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

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

Questo trucco può darti un'idea della logica alla base della funzione, ma ciò che effettivamente accade con il yield è significativamente diverso da ciò che accade nell'approccio basato sull'elenco. In molti casi, l'approccio di rendimento sarà molto più efficiente in termini di memoria e anche più veloce. In altri casi questo trucco ti farà rimanere bloccato in un ciclo infinito, anche se la funzione originale funziona perfettamente. Continuate 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 passaggi:

  1. Ottiene un iteratore per mylist :

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

    [Questo è il passo che molte persone dimenticano di dirti]

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

    Continua a chiamare il metodo next() sull'iterator restituito dal passaggio 1. Il valore restituito da next() viene assegnato a x e il corpo del loop viene eseguito. Se viene sollevata un'eccezione StopIteration dall'interno 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 desidera eseguire il looping dei contenuti 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 è iterabile perché implementa il protocollo iteratore. In una classe definita dall'utente, è possibile implementare il __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__() return self . Ciò funzionerà per casi semplici, ma non quando si desidera che due iteratori eseguano il looping 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 ciclo for non conosce il tipo di oggetto con cui si sta occupando - segue solo il protocollo iteratore ed è felice di ottenere item after item come chiama next() . Gli elenchi integrati restituiscono i loro articoli uno per uno, i dizionari restituiscono le chiavi una alla volta, i file restituiscono le righe una per una, ecc. E i generatori restituiscono ... beh, è ​​qui che arriva la yield :

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

Invece di dichiarazioni di yield , se avessi tre istruzioni di return in f123() solo il primo verrebbe eseguito e la funzione f123() . Ma f123() non è una funzione ordinaria. Quando viene chiamato f123() , non restituisce alcun valore 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 loop sull'oggetto generatore, la funzione riprende dallo stato sospeso alla riga successiva dopo il yield è ritornata in precedenza, 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 solleva StopIteration e il loop termina.

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

Perché usare i generatori?

Di solito è possibile scrivere codice che non utilizza 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 esempio se si hanno cicli infiniti o se si fa un uso inefficiente della memoria quando si ha una lista molto lunga. L'altro approccio consiste nell'implementare una nuova classe iterabile SomethingIter che mantiene lo stato nei membri di istanza ed esegue il prossimo 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.


Ecco alcuni esempi di Python su come implementare realmente 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))

Usando chiusure lessicali invece di 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)

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 mantiene in memoria l'intera lista in una sola volta. Non è un grosso problema per una lista con 5 elementi, ma cosa succede se vuoi una lista 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, la barra 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 in memoria nello stesso momento; l'oggetto generatore "ricorda" dove si trovava nel loop 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 memorizzare i 50 miliardi di numeri per contare. Ancora una volta, questo è un esempio abbastanza forzato,probabilmente useresti itertools se volessi davvero contare fino a 50 miliardi. :)

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


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

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

for i in simple_generator():
    print i

semplicemente uscite

one
two
three

Il potere deriva dall'usare il generatore con un loop 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 è la memoria salvato per calcoli particolarmente grandi

Supponiamo che tu voglia creare una tua rangefunzione che produca una gamma 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 usarlo in questo modo;

for i in myRangeNaive(10):
    print i

Ma questo è inefficiente perché

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

Fortunatamente Guido e il suo team sono stati abbastanza generosi da sviluppare 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 fino a quando non raggiunge un'istruzione 'yield' in cui si arresta e 'produce' il valore o raggiunge la fine della funzione. In questo caso alla prima chiamata, next()esegue la dichiarazione di rendimento e restituisce '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 ritorna falsa e il generatore salta alla fine della funzione.


* significa ricevere argomenti variabili come lista

** significa ricevere argomenti variabili come dizionario

Usato come il seguente:

1) singolo *

def foo(*args):
    for arg in args:
        print(arg)

foo("two", 3)

Produzione:

two
3

2) Ora **

def bar(**kwargs):
    for key in kwargs:
        print(key, kwargs[key])

bar(dic1="two", dic2=3)

Produzione:

dic1 two
dic2 3






python iterator generator yield coroutine