Domanda Cosa fa la parola chiave "rendimento"?


Qual è l'uso del yield parola chiave 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 il metodo _get_child_candidates è chiamato? È 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 .


8309
2017-10-23 22:21


origine


risposte:


Per capire cosa yield fa, tu devi capire cosa generatori  siamo. E prima che arrivino i generatori iterabili .

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 si utilizza una comprensione di lista, si crea 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..."on è un iterable; lists, strings, File...

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

generatori

I generatori sono iteratori, una 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 tu l'hai usato () invece di []. Ma tu non può  eseguire for i in mygenerator una seconda volta poiché i generatori possono essere utilizzati solo una volta: calcolano 0, quindi dimenticano e calcolano 1 e terminano il calcolo 4, uno per uno.

dare la precedenza

yield è una parola chiave che viene utilizzata come return, ad eccezione della 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, devi capirlo quando chiami la funzione, il codice che hai scritto nel corpo della funzione non viene eseguito.  La funzione restituisce solo l'oggetto generatore, questo è un po 'complicato :-)

Quindi, il tuo codice verrà eseguito ogni volta for usa il generatore.

Ora la parte difficile:

La prima volta il for chiama l'oggetto generatore creato dalla tua funzione, eseguirà il codice nella tua funzione dall'inizio fino a quando non colpisce 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 viene eseguita, ma non colpisce yield più. Può essere perché il ciclo si è concluso, o perché non lo si soddisfa "if/else" più.


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 generatori che produrranno valori diversi dai precedenti poiché non è applicato sullo stesso nodo.

  • Il extend() metodo è 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 molti 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, utilizzare print(corner_street_atm.__next__()) o print(next(corner_street_atm))

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

Itertools, il tuo migliore amico

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

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 __iter__() metodo) e iteratori (implementando il __next__() metodo). Iterables sono tutti gli oggetti da cui è possibile ottenere un iteratore. Iterator sono oggetti che ti permettono di scorrere su iterabili.

C'è dell'altro su questo in merito a questo articolo Come for cicli di lavoro .


12169
2017-10-23 22:48



Collegamento a Grokking   yield

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

  1. Inserisci una linea result = [] all'inizio della funzione.
  2. Sostituisci ciascuno yield expr con result.append(expr).
  3. Inserisci una linea return result nella parte inferiore della funzione.
  4. Sì, non di più yield dichiarazioni! 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 con cosa succede realmente 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

Prima il protocollo iteratore  - quando scrivi

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

Python esegue i seguenti due passaggi:

  1. Ottiene un iteratore per mylist:

    Chiamata iter(mylist) -> questo restituisce un oggetto con a next() metodo (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 next() metodo sull'iteratore restituito dal passaggio 1. Il valore restituito da next() è assegnato a x e il corpo del ciclo viene eseguito. Se un'eccezione StopIteration viene sollevato dall'interno next(), significa che non ci sono più valori nell'iteratore e il ciclo è terminato.

La verità è che Python esegue i suddetti due passaggi ogni volta che lo desidera loop over  il contenuto di un oggetto - quindi potrebbe essere un ciclo for, ma potrebbe anche essere un codice simile otherlist.extend(mylist) (dove otherlist è una lista di Python).

Qui mylist è un iterabile  perché implementa il protocollo iteratore. In una classe definita dall'utente, è possibile implementare il __iter__() metodo per rendere le istanze della tua classe iterabili. Questo metodo dovrebbe restituire un iteratore . Un iteratore è un oggetto con a next() metodo. È possibile implementare entrambi __iter__() e next() sulla stessa classe, e hanno __iter__() ritorno 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 a for loop non sa che tipo di oggetto ha a che fare - segue solo il protocollo iteratore, ed è felice di ottenere oggetti dopo voce come chiama next(). Gli elenchi integrati restituiscono i loro articoli uno per uno, i dizionari restituiscono il chiavi  uno per uno, i file restituiscono il Linee  uno per uno, ecc. E i generatori ritornano ... beh, ecco dove yield entra:

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

Invece di yield dichiarazioni, se ne avessi tre return dichiarazioni in f123() solo il primo verrebbe eseguito e la funzione uscirebbe. Ma f123() non è una funzione ordinaria. quando f123() è chiamato, esso non  restituire 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 for loop tenta di eseguire il loop sull'oggetto generatore, la funzione riprende dallo stato sospeso alla riga successiva dopo il yield da cui è ritornato in precedenza, esegue la riga successiva del codice, in questo caso a yield dichiarazione e la restituisce come articolo successivo. Questo accade fino a quando la funzione non si chiude, a quel punto il generatore si alza StopIteratione il ciclo termina.

Quindi l'oggetto generatore è un po 'come un adattatore - ad una estremità esibisce il protocollo iteratore, esponendo __iter__() e next() metodi per mantenere il for ciclo felice. 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 è implementare una nuova classe iterabile SomethingIter che mantiene lo stato nei membri di istanza ed esegue il successivo passo logico in esso next() (o __next__() in Python 3) metodo. A seconda della logica, il codice all'interno di next() il metodo potrebbe sembrare molto complesso e soggetto a bug. Qui i generatori forniscono una soluzione semplice e pulita.


1635
2017-10-25 21:22



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 ciò che accade dietro le quinte, il for loop 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ù? :)

Dovrei notare che questo è  una semplificazione eccessiva a scopi illustrativi. :)


396
2017-10-23 22:28



Il yield la parola chiave è ridotta a due semplici fatti:

  1. Se il compilatore rileva il yield parola chiave dovunque  all'interno di una funzione, quella funzione non ritorna più attraverso il return dichiarazione. Anziché , esso subito  restituisce a Oggetto "lista pendente" pigro  chiamato un generatore
  2. Un generatore è iterabile. Cos'è un iterabile ? È tutto come un list o set o range o dict-view, con a protocollo integrato per visitare ogni elemento in un determinato ordine .

In poche parole: un generatore è un elenco pigro, in pendenza crescente , e yield le istruzioni consentono di utilizzare la notazione della funzione per programmare i valori della lista  il generatore dovrebbe 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 è proprio come Python range. chiamata 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 list() (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 puoi usare i generatori

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

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

Per avere una sensazione migliore per i generatori, puoi giocare con il itertools modulo (assicurati di usare chain.from_iterable piuttosto che chain quando giustificato). Ad esempio, potresti anche utilizzare i generatori per implementare liste pigro infinitamente simili itertools.count(). Potresti implementare il tuo def enumerate(iterable): zip(count(), iterable)o in alternativa farlo con yieldparola chiave in un ciclo temporale.

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


Dietro le quinte

Questo è il modo in cui funziona il "protocollo di iterazione Python". Cioè, cosa sta succedendo quando lo 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 integrata next() chiama solo gli oggetti .next() funzione, che fa parte del "protocollo di iterazione" e si trova su tutti gli iteratori. È possibile utilizzare manualmente il next() funzione (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-parlare, un iterabile  è qualsiasi oggetto che "capisca il concetto di un ciclo for" come una lista [1,2,3], e un iteratore  è un'istanza specifica del ciclo richiesto come [1,2,3].__iter__(). UN Generatore  è esattamente uguale a qualsiasi iteratore, tranne per il modo in cui è stato scritto (con la sintassi della funzione).

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

Quindi, nell'improbabile caso che tu non riesca a fare qualcosa 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 myRange(...) ancora. Se è necessario utilizzare il risultato due volte, convertire il risultato in un elenco e memorizzarlo in una variabile x = list(myRange(5)). Coloro che hanno assolutamente bisogno di clonare un generatore (ad esempio, chi sta facendo terrificante metaprogrammazione hacker) possono usare itertools.tee se assolutamente necessario, dal momento che l'autore iteratore di copia Python PEP  la proposta di standard è stata rinviata.


346
2018-06-19 06:33



Cosa fa il yield parola chiave fai in Python?

Risposta Struttura / Riepilogo

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

generatori:

yield è legale solo all'interno di una definizione di funzione, e l'inclusione di yield in una definizione di funzione fa ritornare un generatore.

L'idea per i generatori viene da altre lingue (vedi nota 1) con diverse implementazioni. In Python's Generators, l'esecuzione del codice è congelato  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 di implementando il protocollo iteratore , definito dai seguenti due metodi: __iter__ e next (Python 2) o __next__ (Python 3). Entrambi questi metodi rendere un oggetto un iteratore che è possibile digitare-verificare con il Iterator Base astratta Classe dal collections modulo.

>>> 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  è una volta esaurito , non puoi 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

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

coroutine:

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

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

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


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

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

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

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

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

Delegazione cooperativa a sottoclorocezione con yield from

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

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


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

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

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

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

Altri metodi: chiudere e lanciare

Il close metodo solleva GeneratorExit al punto la funzione l'esecuzione è stata congelata. Questo sarà anche chiamato da __del__ quindi tu può inserire qualsiasi codice di pulizia in cui gestisci il GeneratorExit:

>>> my_account.close()

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

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

Conclusione

Credo di aver coperto tutti gli aspetti della seguente domanda:

Cosa fa il yield parola chiave fai in Python?

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


Appendice:

Critica della risposta in alto / accettata **

  • È confuso su ciò che rende un iterabile , semplicemente usando un elenco come esempio. Vedi i miei riferimenti sopra, ma in sintesi: un iterabile ha un __iter__ metodo restituendo un iteratore . Un iteratore  fornisce a .next (Python 2 o .__next__ (Python 3), che viene implicitamente chiamato da forloop finché non si alza StopIteratione una volta eseguita, continuerà a farlo.
  • Quindi utilizza un'espressione di generatore per descrivere cosa è un generatore. Dal momento che un generatore è semplicemente un modo conveniente per creare un iteratore , confonde solo la questione, e non abbiamo ancora ottenuto il yield parte.
  • In Controllo dell'esaurimento del generatore  lui chiama il .next metodo, quando invece dovrebbe usare la funzione built-in, next. Sarebbe uno strato appropriato di riferimento indiretto, perché il suo codice non funziona in Python 3.
  • Itertools? Questo non era rilevante per cosa yield a tutti.
  • Nessuna discussione sui metodi yield fornisce insieme alla nuova funzionalità yield from in Python 3. La risposta superiore / accettata è una risposta molto incompleta.

Critica di risposta che suggerisce 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 lo sono discutere 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     la sintassi.

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

In termini di 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 numero in sospeso (10544)  che sembra puntare nella direzione di questo mai  essere una buona idea (PyPy, un'implementazione Python scritta in Python, sta già sollevando avvisi di sintassi).

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

Il return dichiarazione in un generatore

In Python 2 :

In una funzione generatore, il return la dichiarazione non è autorizzata a includere un expression_list. In quel contesto, a nudo return indica che il generatore è fatto e causerà StopIteration essere cresciuto

Un expression_list è fondamentalmente 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 generatore, il return la dichiarazione indica che il generatore è fatto e causerà StopIteration essere cresciuto Il valore restituito (se presente) viene utilizzato come argomento da costruire StopIteration e diventa il 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 un livello intermedio punti dati su richiesta dell'utente. Questo ha promesso di essere superiore nelle prestazioni ad altri approcci, incluso il threading di Python , che non è nemmeno disponibile su alcuni sistemi.

  2.  Questo significa, per esempio, quello xrange oggetti ( range in Python 3) non lo sono Iterators, anche se sono iterabili, perché possono essere riutilizzati. Mi piacciono le liste, le loro __iter__ metodi restituiscono oggetti iteratore.

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


253
2018-06-25 06:11



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 a yield dichiarazione. 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_candidates si comporta come un iteratore in modo che quando si estende l'elenco, 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.


230
2017-10-23 22:24



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.


182
2017-10-24 08:44



Per coloro che preferiscono un esempio di lavoro minimo, medita su questo interattivo Pitone  sessione:

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

155
2018-01-18 17:25