it-swarm.dev

Cosa fa la parola chiave "rendimento"?

Qual è l'uso della parola chiave yield in Python? Che cosa fa?

Ad esempio, sto cercando di capire questo codice1:

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? Viene 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 .

8943
Alex. S.

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

Iterabili

Quando crei un elenco, puoi leggere i suoi elementi uno per uno. La lettura dei suoi articoli uno per uno è chiamata iterazione:

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

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

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

Tutto ciò che puoi usare "for... in..." su è un iterable; lists, strings, files ...

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 sorta 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, tranne che hai usato () invece di []. MA, tu non puoi eseguire for i in mygenerator una seconda volta poiché i generatori possono essere usati solo una volta: calcolano 0, poi dimenticano e calcolano 1, e terminano il calcolo 4, uno per uno.

Dare la precedenza

yield è una parola chiave che viene utilizzata come return, tranne che la funzione 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

Qui è un esempio inutile, ma è utile quando si sa che la funzione restituirà un enorme set di valori che sarà necessario leggere solo una volta.

Per padroneggiare yield, è necessario comprendere che quando si chiama la funzione, il codice 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 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ù yield. Può essere perché il ciclo si è concluso, o perché non hai più soddisfatto "if/else".


Il tuo codice 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 passare attraverso 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 alla lista.

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 un sacco di bambini 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 '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, usareprint(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 dell'iterazione

L'iterazione è un processo che implica iterabili (implementando il metodo __iter__()) ed iteratori (implementando il metodo __next__()) . Iterables sono tutti gli oggetti dai quali è possibile ottenere un iteratore. Iterator sono oggetti che ti permettono di scorrere su iterabili.

C'è di più in questo articolo su come funziona for loops .

13125
e-satis

Collegamento a Grokkingyield

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

  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. Sì, niente più dichiarazioni 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 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. Continuate a leggere per saperne di più...

Non confondere i tuoi Iterables, Iterators e Generators

Innanzitutto, il protocollo iterator - quando scrivi

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

Python esegue i seguenti due passaggi:

  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 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 di ritorno da next() viene assegnato a x e il corpo del loop viene eseguito. Se un'eccezione StopIteration viene sollevata all'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 passi ogni volta che vuole loop over i contenuti di un oggetto - quindi potrebbe essere un ciclo for, ma potrebbe anche essere un codice come otherlist.extend(mylist) (dove otherlist è una lista Python ).

Qui mylist è un iterabile perché implementa il protocollo iteratore. In una classe definita dall'utente, puoi implementare il metodo __iter__() per rendere le istanze della tua classe iterabili. 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 effettuino il looping sullo stesso oggetto nello stesso momento.

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 trattando - segue solo il protocollo iteratore ed è felice di ottenere elementi dopo l'articolo poiché chiama next(). Gli elenchi integrati restituiscono i loro articoli uno per uno, i dizionari restituiscono keys uno per uno, i file restituiscono righe uno per uno, ecc. E i generatori ritornano ... beh, ecco dove yield è disponibile in:

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

Invece delle istruzioni yield, se avevi tre istruzioni return in f123(), solo il primo verrebbe eseguito e la funzione uscirebbe. Ma f123() non è una funzione ordinaria. Quando viene chiamato f123(), does not restituisce uno qualsiasi 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 il yield da cui è stato precedentemente 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 termina. 

Quindi l'oggetto generatore è un po 'come un adattatore - ad una estremità mostra il protocollo iteratore, esponendo i metodi __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 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.

1751
user28409

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 accade 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 questo è una semplificazione eccessiva a scopi illustrativi. :)

445
Jason Baker

La parola chiave yield è ridotta a due semplici fatti:

  1. Se il compilatore rileva la _ yield keyword anywhere all'interno di una funzione, quella funzione non ritorna più tramite l'istruzione return. Invece, it immediately restituisce un oggetto lazy "lista pendente" chiamato un generatore
  2. Un generatore è iterabile. Cosa è un iterable? È simile a un list o set o range o dict-view, con un protocollo incorporato per la visita di ogni elemento in un determinato ordine.

In breve: un generatore è un elenco pigro, in pendenza incrementale, e le istruzioni yield consentono di usare la notazione della funzione per programmare i valori della lista che il generatore deve sputare in modo incrementale.

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 simile a range di Python. Chiamare 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 in list() (proprio come qualsiasi altro iterabile):

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

Confronto di esempio con "solo restituendo una lista"

L'esempio sopra può essere pensato semplicemente come la creazione di una lista a cui si aggiunge e 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 usare i generatori

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

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

Per avere un'idea migliore dei generatori, puoi giocare con il modulo itertools (assicurati di usare chain.from_iterable piuttosto che chain quando richiesto). Ad esempio, potresti persino utilizzare i generatori per implementare elenchi pigri infinitamente lunghi come itertools.count(). È possibile implementare il proprio 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 implementando 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 troverete.


Dietro le quinte

Questo è il modo in cui funziona il "protocollo di iterazione Python". Cioè, cosa sta succedendo quando fai list(makeRange(5)). Questo è ciò che descrivo in precedenza 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 built-in next() chiama solo la funzione .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 qualsiasi oggetto che "capisce il concetto di un ciclo for" come una lista [1,2,3], e un iteratore è un'istanza specifica della richiesta richiesta- loop come [1,2,3].__iter__(). A generator è 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 del genere ...

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

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

380
ninjagecko

yield è proprio come return - restituisce qualsiasi cosa tu gli dica (come generatore). La differenza è che la prossima volta che chiamate il generatore, l'esecuzione inizia dall'ultima chiamata all'istruzione yield. Diversamente da return, 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.

260
Douglas Mayle

C'è una cosa in più da menzionare: 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 su alcune cose. 

199
Claudiu

Per coloro che preferiscono un esempio di lavoro minimo, meditate su questa sessione interattiva 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
171
Daniel

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 invece ogni pezzo. 

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


yield è un zuccherino modo di 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 solo ripetere una volta. Quando una funzione ha una resa in essa, la chiamiamo funzione generatore . E un iteratore è ciò che restituisce. Quei termini sono rivelatori. Perdiamo la convenienza di un contenitore, ma otteniamo la potenza di una serie calcolata in base alle esigenze e arbitrariamente lunga.

La resa è lazy, mette fuori calcolo. Una funzione con un rendimento in esso in realtà non viene eseguita quando la si chiama. Restituisce un oggetto iteratore che ricorda da dove era stato interrotto. Ogni volta che si chiama next() sull'iteratore (ciò accade in un ciclo for), l'esecuzione di inchinerà in avanti alla resa successiva. 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ù pass e la serie non è troppo lunga, basta chiamare list() su di essa:

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

Scelta geniale della parola yield perché entrambi i significati si applicano:

yield - produrre o fornire (come in agricoltura)

... fornire i prossimi dati della serie.

yield - cedere o abbandonare (come nel potere politico)

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

160
Bob Stein

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 si desidera un elenco di 5 milioni? Non solo è un enorme mangiatore 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 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 memorizzare i 50 miliardi di numeri per contare. Di nuovo, questo è un esempio abbastanza ingegnoso, 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 permute 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.

148
RBansal

Sta restituendo un generatore. Non ho molta familiarità con Python, ma credo che sia lo stesso tipo di i blocchi iteratori 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à 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ù facile che scrivere un iteratore da solo.

145
Jon Skeet

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'istruzione yield in Python restituisce un generatore. Un generatore in Python è una funzione che ritorna 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 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 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. Considerando che continuations in generale è in grado di salvare lo stato di un computazione (cioè lo stack di chiamate del programma), i generatori sono in grado di salvare lo stato di iterazione 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 prototipale di iterable 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 capire 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 di continuazione:

Ogni volta che viene chiamata 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 oggetti generatori senza utilizzare la parola chiave yield.

134
aestrivex

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 passo corrisponde a defining della funzione del generatore, ad esempio la funzione contenente un 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; 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.
  • ... 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 un'eccezione StopIteration La funzione generatore non ha bisogno di aumentare l'eccezione. Viene generato 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 ferma 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 defining una funzione 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 iteratori , dichiarazione di rendimento e generatori nella documentazione di Python.

120
tzot

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

Per aiutare a capire che cosa fa yield nel seguente codice, puoi usare il tuo dito per tracciare il ciclo attraverso qualsiasi codice che abbia un yield. Ogni volta che il dito colpisce yield, devi aspettare un next o un send da inserire. Quando viene chiamato un next, si traccia attraverso il codice finché non si preme yield ... il codice a destra del yield viene valutato e restituito al chiamante ... quindi si attende. Quando next viene chiamato di nuovo, 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 dalla funzione caller in the yielding. Se viene fornito un send, yield riceve il valore inviato e lo sputa dal lato sinistro ... quindi la traccia attraverso il codice progredisce finché non si preme di nuovo yield (restituendo il valore alla fine, come se fosse stato chiamato next).

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()
105
Mike McKerns

Esiste un altro uso e significato yield (da Python 3.3):

yield from <expr>

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

Viene proposta 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 this 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 yield è usato in entrambi).

97
Sławomir Lenart

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

Presumo che tu abbia imparato l'istruzione return.

Come un'analogia, return e yield sono gemelli. return significa 'return and stop' mentre 'yield' significa 'return, 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. return non ti consente mai di prevalere felicemente, implementa solo una volta e esce.

  1. Arriva yield

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.

Confrontando return che viene eseguito una volta e interrompe, yield esegue le volte che hai pianificato . Puoi 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
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 output return di lista e l'output yield dell'oggetto è:

Otterrai sempre [0, 1, 2] da un oggetto elenco ma solo potrai recuperarli dall'oggetto yield output 'una volta. Quindi, ha un nuovo nome generator oggetto come visualizzato 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
86
JawSaw

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

Usare 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)
83
Dustin Getz

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

Inoltre, si noti che 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.

81
johnzachary

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), uno utilizza 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
->
77
alinsoar

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

Sembra essere un'abilità interessante e piacevole: D

70
Engin OZTURK

Ecco un'immagine mentale di cosa fa yield.

Mi piace pensare a un thread come a uno stack (anche quando 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 funzione yield, quando il suo codice inizia a funzionare (vale a dire dopo che la funzione è stata richiamata, 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 è attualmente nel suo codice (cioè la specifica yield istruzione).

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 trascorso 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'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>

Chiamare yielderFunction() non esegue il suo codice, ma 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 lo stato congelato è memorizzato. Esplorandoli con dir(..), possiamo confermare che il nostro modello mentale sopra è credibile.

58
Evgeni Sergeev

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

Execution Control Transfer gotcha

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"
49

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 è un barrier .

come barriera nel linguaggio CUDA, non trasferirà il controllo finché non ottiene completato.

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.

43
Kaleem Ullah

In breve, l'istruzione yield trasforma la tua funzione in una factory che produce un oggetto speciale chiamato generator che avvolge il corpo della tua funzione originale. Quando generator è iterato, esegue la tua funzione fino a raggiungere 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. 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 fermandosi 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 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 usarlo in questo modo;

for i in myRangeNaive(10):
    print i

Ma questo è inefficiente perché

  • Crei una matrice 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 chiamata 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 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à così finché la condizione while non ritorna falsa e il generatore salta alla fine della funzione.

42
redbandit

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

Ecco un esempio che yield è sicuramente il migliore per:

return (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

yield (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

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 utilizza tre righe anziché cinque e ha una variabile in meno di cui preoccuparsi.

Questo è il risultato del codice:

 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 si desidera creare un generatore.

40
Tom Fuller

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. È possibile ottenere una sequenza di tutti i valori 'yielded' in uno, chiamando list(generator()).

35
Theoremiser

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

35
Bahtiyar Özdere

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 REPL e poi prova a chiamarlo, otterrai un risultato mistificante:

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

Questo perché la presenza di yield segnala a Python che si desidera creare un generator , 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 fib a un ciclo 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 ne verranno prodotti altri ( e ritorna):

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 differisce 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 fib chiamandolo:

f = fib()

Python compila la funzione, incontra la parola chiave yield 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 che hai fornito a yield e le pause. 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 ora 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 nuovamente next 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 su yield value e riprende da lì. Il prossimo messaggio viene stampato e la ricerca dell'istruzione yield per metterla in pausa viene eseguita di nuovo (a causa del ciclo while).

32

Un semplice esempio di ciò che è facilmente spiegabile: 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
29
Gavriel Cohen

Ancora un altro TL; DR

Iterator sulla lista: next() restituisce il prossimo elemento della lista

Iterator generator: next() calcola l'elemento successivo al volo (esegui codice)

Puoi vedere il rendimento/generatore come un modo per eseguire manualmente il flusso 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. Si potrebbe fare a meno di yield, ma non sarebbe così bello, quindi può essere considerato zucchero di lingua "molto bello".

28

yield è simile a return. La differenza è: 

yield rende una funzione iterabile (nell'esempio seguente la funzione primes(n = 1) diventa iterabile).
Che cosa significa in sostanza è la prossima volta che la funzione viene chiamata, continuerà da dove è andata (che è dopo la riga di yield expression).

def isprime(n):
    if n == 1:
        return False
    for x in range(2, n):
        if n % x == 0:
            return False
    else:
        return True

def primes(n = 1):
   while(True):
       if isprime(n): yield n
       n += 1 

for n in primes():
    if n > 100: break
    print(n)

Nell'esempio precedente se isprime(n) è true restituirà il numero primo. Nella prossima iterazione continuerà dalla riga successiva 

n += 1  
24
blueray

Tutte le risposte qui sono grandi; ma solo uno di essi (il più votato) si riferisce a come funziona il tuo codice . Altri sono relativi ai generatori in generale e al loro funzionamento.

Quindi non ripeterò cosa sono i generatori o cosa fanno i rendimenti; Penso che questi siano coperti da grandi risposte esistenti. Tuttavia, dopo aver trascorso alcune ore a cercare di capire un codice simile al tuo, lo analizzerò come funziona.

Il tuo codice attraversa una struttura ad albero binario. Prendiamo questo albero per esempio:

    5
   / \
  3   6
 / \   \
1   4   8

E un'altra implementazione più semplice di un attraversamento dell'albero di ricerca binaria:

class Node(object):
..
def __iter__(self):
    if self.has_left_child():
        for child in self.left:
            yield child

    yield self.val

    if self.has_right_child():
        for child in self.right:
            yield child

Il codice di esecuzione si trova sull'oggetto Tree, che implementa __iter__ come questo:

def __iter__(self):

    class EmptyIter():
        def next(self):
            raise StopIteration

    if self.root:
        return self.root.__iter__()
    return EmptyIter()

L'istruzione while candidates può essere sostituita con for element in tree; Python traduce questo in

it = iter(TreeObj)  # returns iter(self.root) which calls self.root.__iter__()
for element in it: 
    .. process element .. 

Poiché la funzione Node.__iter__ è un generatore, il codice inside it viene eseguito per iterazione. Quindi l'esecuzione sarebbe simile a questa:

  1. l'elemento radice è il primo; controlla se ha lasciato childs e for iterali (chiamiamolo it1 perché è il primo oggetto iteratore)
  2. ha un figlio quindi viene eseguita for. for child in self.left crea un nuovo iteratore da self.left, che è un oggetto Nodo stesso (it2)
  3. Stessa logica di 2 e viene creata una nuova iterator (it3)
  4. Ora abbiamo raggiunto l'estremità sinistra dell'albero. it3 non ha figli rimasti, quindi continua e yield self.value
  5. Alla prossima chiamata a next(it3) solleva StopIteration ed esiste poiché non ha figli giusti (raggiunge la fine della funzione senza produrre nulla)
  6. it1 e it2 sono ancora attivi - non sono esauriti e chiamare next(it2) produrrebbe valori, non alzerebbe StopIteration
  7. Ora torniamo al contesto it2 e chiamiamo next(it2) che continua dove si è fermato: subito dopo l'istruzione yield child. Dal momento che non ha più figli lasciati continua e produce self.val.

Il problema qui è che ogni iterazione crea sub-iteratori per attraversare l'albero e mantiene lo stato dell'attuale iteratore. Una volta raggiunta la fine, attraversa di nuovo lo stack e i valori vengono restituiti nell'ordine corretto (valore di rendimento più piccolo per primo).

Il tuo esempio di codice ha fatto qualcosa di simile con una tecnica diversa: popolato un elenco di un elemento per ogni bambino, quindi alla successiva iterazione lo apre ed esegue il codice di funzione sull'oggetto corrente (da cui self).

Spero che questo abbia contribuito un po 'a questo argomento leggendario. Ho passato diverse ore a disegnare questo processo per capirlo.

10
Chen A.

In breve, l'uso di yield è simile alla parola chiave return, tranne che restituisce un generator .
Un oggetto generator attraversa solo once.

yield ha due vantaggi: 

  1. Non è necessario leggere questi valori due volte; 
  2. È possibile ottenere molti nodi figlio senza metterli tutti in memoria.
8
123

In Python generators (un tipo speciale di iterators) sono usati per generare serie di valori e la parola chiave yield è proprio come la parola chiave return delle funzioni del generatore. 

L'altra cosa affascinante yield keyword fa sta salvando state di una funzione di generatore

Quindi, possiamo impostare number su un valore diverso ogni volta che generator produce. 

Ecco un'istanza:

def getPrimes(number):
    while True:
        if isPrime(number):
            number = yield number     # a miracle occurs here
        number += 1

def printSuccessivePrimes(iterations, base=10):
primeGenerator = getPrimes(base)
primeGenerator.send(None)
for power in range(iterations):
    print(primeGenerator.send(base ** power))
7
ARGeo

Dare la precedenza

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

In breve , puoi vedere che il ciclo non si ferma e continua a funzionare anche dopo l'invio dell'oggetto o della variabile (diversamente da return dove il ciclo si ferma dopo l'esecuzione).

6
Gavriel Cohen

Un'analogia potrebbe aiutare a cogliere l'idea qui:

Immagina di aver creato una macchina straordinaria capace di generare migliaia e migliaia di lampadine al giorno. La macchina genera queste lampadine in scatole con un numero di serie univoco. Non hai abbastanza spazio per immagazzinare tutte queste lampadine contemporaneamente (cioè, non puoi tenere il passo con la velocità della macchina a causa della limitazione dello spazio di archiviazione), quindi ti piacerebbe regolare questa macchina per generare lampadine su richiesta.

I generatori Python non differiscono molto da questo concetto.

Immagina di avere una funzione x che genera numeri di serie univoci per le scatole. Ovviamente, è possibile avere un numero molto elevato di tali codici a barre generati dalla funzione. Un'opzione più saggia ed efficiente nello spazio consiste nel generare quei numeri seriali su richiesta.

Codice della macchina:

def barcode_generator():
    serial_number = 10000  # Initial barcode
    while True:
        yield serial_number
        serial_number += 1


barcode = barcode_generator()
while True:
    number_of_lightbulbs_to_generate = int(input("How many lightbulbs to generate? "))
    barcodes = [next(barcode) for _ in range(number_of_lightbulbs_to_generate)]
    print(barcodes)

    # function_to_create_the_next_batch_of_lightbulbs(barcodes)

    produce_more = input("Produce more? [Y/n]: ")
    if produce_more == "n":
        break

Come puoi vedere abbiamo una "funzione" autonoma per generare ogni volta il prossimo numero seriale univoco. Questa funzione restituisce un generatore! Come puoi vedere, non chiamiamo la funzione ogni volta che abbiamo bisogno di un nuovo numero seriale, ma stiamo usando next() dato il generatore per ottenere il prossimo numero seriale.

Produzione:

How many lightbulbs to generate? 5
[10000, 10001, 10002, 10003, 10004]
Produce more? [Y/n]: y
How many lightbulbs to generate? 6
[10005, 10006, 10007, 10008, 10009, 10010]
Produce more? [Y/n]: y
How many lightbulbs to generate? 7
[10011, 10012, 10013, 10014, 10015, 10016, 10017]
Produce more? [Y/n]: n
6
Rafael

yield È un tipo di generatore che può essere utilizzato in python.

ecco un link per vedere cosa fa veramente Yield, anche in generazione . Generatori e parole chiave Yield - Python Central (PC)

Anche yield funziona come return, ma in un modo diverso da return. Anche se c'è un link che spiega yield di più, Se non capisci l'altro non così bene. Migliora la tua abilità di rendimento - jeffknupp

In parole semplici, 'yield' è simile a 'return' un valore, ma funziona su Generator.

1
user3701435

Nel rendimento semplice restituisce l'oggetto generatore anziché i valori. 

Sotto semplice esempio aiuterà!

def sim_generator():
    for i in range(3):
        yield(i)

obj = sim_generator()
next(obj) # another way is obj.__next__()
next(obj)
next(obj)

il codice sopra riportato restituisce 0, 1, 2

o anche breve

for val in sim_generator():
    print(val)

restituisce 0, 1, 2

Spero che questo ti aiuti

1
Vivek Ananthan

Una semplice funzione di generatore

def my_gen():
    n = 1
    print('This is printed first')
    # Generator function contains yield statements
    yield n

    n += 1
    print('This is printed second')
    yield n

    n += 1
    print('This is printed at last')
    yield n

dichiarazione di rendimento sospende la funzione salvando tutti i suoi stati e successivamente continua da lì su chiamate successive.

https://www.programiz.com/python-programming/generator

0
Savai Maheshwari

yield produce qualcosa. È come se qualcuno ti chiedesse di fare 5 cup cakes. Se hai finito con almeno una tazza di torta, puoi darle a mangiare mentre fai altre torte.

In [4]: def make_cake(numbers):
   ...:     for i in range(numbers):
   ...:         yield 'Cake {}'.format(i)
   ...:

In [5]: factory = make_cake(5)

Qui factory è chiamato generatore, che ti fa torte. Se chiami make_function, ottieni un generatore invece di eseguire quella funzione. È perché quando la parola chiave yield è presente in una funzione, diventa un generatore.

In [7]: next(factory)
Out[7]: 'Cake 0'

In [8]: next(factory)
Out[8]: 'Cake 1'

In [9]: next(factory)
Out[9]: 'Cake 2'

In [10]: next(factory)
Out[10]: 'Cake 3'

In [11]: next(factory)
Out[11]: 'Cake 4'

Hanno consumato tutte le torte, ma ne chiedono ancora una.

In [12]: next(factory)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-12-0f5c45da9774> in <module>
----> 1 next(factory)

StopIteration:

e viene loro detto di smettere di chiedere di più. Quindi, una volta che hai consumato un generatore, hai finito. Hai bisogno di chiamare make_cake di nuovo se vuoi più torte. È come mettere un altro ordine per le torte della tazza.

In [13]: factory = make_cake(3)

In [14]: for cake in factory:
    ...:     print(cake)
    ...:
Cake 0
Cake 1
Cake 2

Puoi anche usare per il ciclo con un generatore come quello sopra.

Un altro esempio: diciamo che vuoi una password casuale ogni volta che la chiedi.

In [22]: import random

In [23]: import string

In [24]: def random_password_generator():
    ...:     while True:
    ...:         yield ''.join([random.choice(string.ascii_letters) for _ in range(8)])
    ...:

In [25]: rpg = random_password_generator()

In [26]: for i in range(3):
    ...:     print(next(rpg))
    ...:
FXpUBhhH
DdUDHoHn
dvtebEqG

In [27]: next(rpg)
Out[27]: 'mJbYRMNo'

Qui rpg è un generatore, che può generare un numero infinito di password casuali. Quindi possiamo anche dire che i generatori sono utili quando non conosciamo la lunghezza della sequenza diversa dalla lista che ha un numero finito di elementi.

0
thavan