12901 Stimmen

Was bewirkt das Schlüsselwort "yield"?

Wozu dient die yield Schlüsselwort in Python? Was bewirkt es?

Ich versuche zum Beispiel, folgenden Code zu verstehen 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  

Und das ist der Anrufer:

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

Was passiert, wenn die Methode _get_child_candidates aufgerufen wird? Wird eine Liste zurückgegeben? Ein einzelnes Element? Wird sie erneut aufgerufen? Wann werden die nachfolgenden Aufrufe beendet?


1. Dieses Stück Code wurde von Jochen Schulz (jrschulz) geschrieben, der eine großartige Python-Bibliothek für metrische Räume entwickelt hat. Dies ist der Link zum vollständigen Quellcode: <a href="https://well-adjusted.de/~jrspieker/mspace/" rel="noreferrer">Modul mspace </a>.

50voto

Christophe Roussy Punkte 15183

Noch ein TL;DR

Iterator auf Liste : next() gibt das nächste Element der Liste zurück

Iterator-Generator : next() berechnet das nächste Element während der Ausführung (Code ausführen)

Sie können den Yield/Generator als eine Möglichkeit sehen, die Kontrollfluss von außen (z.B. Schleife um einen Schritt fortsetzen), indem Sie next wie komplex der Fluss auch sein mag.

Hinweis : Der Generator ist NICHT eine normale Funktion. Sie merkt sich den vorherigen Zustand wie lokale Variablen (Stack). Siehe andere Antworten oder Artikel für detaillierte Erklärungen. Der Generator kann nur sein einmal iteriert . Sie könnten auch ohne yield aber es wäre nicht so schön, so dass es als "sehr schöner" Sprachzucker betrachtet werden kann.

39voto

Ahmad Ismail Punkte 8416

Ertrag ist ähnlich wie die Rückkehr. Der Unterschied ist:

Ertrag macht eine Funktion iterierbar (im folgenden Beispiel primes(n = 1) Funktion wird iterierbar).
Das bedeutet im Wesentlichen, dass die Funktion beim nächsten Aufruf dort weitermacht, wo sie aufgehört hat (also nach der Zeile mit 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)

In dem obigen Beispiel, wenn isprime(n) wahr ist, wird die Primzahl zurückgegeben. Bei der nächsten Iteration geht es in der nächsten Zeile weiter

n += 1

31voto

Andy Jazz Punkte 34407

In Python generators (eine besondere Art von iterators ) werden verwendet, um eine Reihe von Werten zu erzeugen und yield ist genau wie das Schlüsselwort return Schlüsselwort der Generatorfunktionen.

Die andere faszinierende Sache yield Schlüsselwortes ist das Speichern der state einer Generatorfunktion .

Wir können also eine number jedes Mal auf einen anderen Wert setzen, wenn die generator Erträge.

Hier ist ein Beispiel:

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

30voto

thavan Punkte 2303

yield gibt etwas her. Es ist so, als ob jemand Sie bittet, 5 Muffins zu backen. Wenn du mit mindestens einem Muffin fertig bist, kannst du ihn demjenigen zum Essen geben, während du andere Kuchen machst.

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

In [5]: factory = make_cake(5)

Hier factory wird als Generator bezeichnet, der Sie zu Kuchen macht. Wenn Sie anrufen make_function erhalten Sie einen Generator, anstatt diese Funktion auszuführen. Das ist so, weil wenn yield Schlüsselwort in einer Funktion vorhanden ist, wird sie zu einem Generator.

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'

Sie haben alle Kuchen verzehrt, aber sie bitten wieder um einen.

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

StopIteration:

und es wird ihnen gesagt, dass sie aufhören sollen, mehr zu verlangen. Wenn Sie also einen Generator verbraucht haben, sind Sie damit fertig. Sie müssen anrufen make_cake wieder, wenn Sie mehr Kuchen wollen. Es ist wie eine weitere Bestellung für Cupcakes.

In [13]: factory = make_cake(3)

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

Sie können auch eine for-Schleife mit einem Generator wie dem oben genannten verwenden.

Ein weiteres Beispiel: Nehmen wir an, Sie wollen ein zufälliges Passwort, wann immer Sie danach fragen.

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'

Hier rpg ist ein Generator, der eine unendliche Anzahl von Zufallspasswörtern erzeugen kann. Wir können also auch sagen, dass Generatoren nützlich sind, wenn wir die Länge der Sequenz nicht kennen, im Gegensatz zu Listen, die eine endliche Anzahl von Elementen haben.

22voto

Chen A. Punkte 8658

Alle Antworten hier sind großartig, aber nur eine von ihnen (die meistgewählte) bezieht sich auf wie Ihr Code funktioniert . Andere beziehen sich auf Stromerzeuger im Allgemeinen und wie sie funktionieren.

Ich werde also nicht wiederholen, was Generatoren sind oder was Renditen bewirken; ich denke, dass diese Fragen in den bereits vorhandenen Antworten ausführlich behandelt werden. Nachdem ich jedoch einige Stunden damit verbracht habe, einen ähnlichen Code wie den Ihren zu verstehen, werde ich aufschlüsseln, wie er funktioniert.

Ihr Code durchläuft eine binäre Baumstruktur. Nehmen wir zum Beispiel diesen Baum:

    5
   / \
  3   6
 / \   \
1   4   8

Und eine weitere, einfachere Implementierung eines binären Suchbaums, der durchlaufen wird:

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

Der Ausführungscode befindet sich auf der Tree Objekt, das Folgendes implementiert __iter__ als dies:

def __iter__(self):

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

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

Le site while candidates Anweisung kann ersetzt werden durch for element in tree ; Python übersetzt dies in

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

Denn Node.__iter__ Funktion ein Generator ist, wird der Code darin wird pro Iteration ausgeführt. Die Ausführung würde also wie folgt aussehen:

  1. Das Wurzelelement steht an erster Stelle; prüfen Sie, ob es linke Kindelemente hat und for Iterieren Sie sie (nennen wir es1, weil es das erste Iteratorobjekt ist)
  2. es hat ein Kind, so dass die for ausgeführt wird. Die Website for child in self.left erstellt eine neuer Iterator von self.left der selbst ein Node-Objekt ist (it2)
  3. Dieselbe Logik wie bei 2, und eine neue iterator erstellt wird (it3)
  4. Jetzt haben wir das linke Ende des Baumes erreicht. it3 hat keine linken Kinder, also geht es weiter und yield self.value
  5. Beim nächsten Aufruf von next(it3) erhebt StopIteration und existiert, da es keine rechten Kindelemente hat (es reicht bis zum Ende der Funktion, ohne etwas zu ergeben)
  6. it1 y it2 noch aktiv sind - sie sind nicht erschöpft und rufen next(it2) würde Werte liefern, nicht erhöhen StopIteration
  7. Jetzt sind wir wieder bei it2 Kontext, und rufen Sie next(it2) der dort weitergeht, wo er aufgehört hat: direkt nach der yield child Erklärung. Da es keine weiteren Kinde mehr hat, fährt es fort und gibt seine self.val .

Der Haken dabei ist, dass jede Iteration erzeugt Unteriteratoren um den Baum zu durchlaufen, und hält den Status des aktuellen Iterators. Sobald das Ende erreicht ist, wird der Stapel zurückverfolgt, und die Werte werden in der richtigen Reihenfolge zurückgegeben (der kleinste Wert zuerst).

Ihr Codebeispiel hat etwas Ähnliches in einer anderen Technik getan: Es füllte eine Ein-Element-Liste für jedes Kind, und bei der nächsten Iteration wird der Funktionscode für das aktuelle Objekt ausgeführt (daher die self ).

Ich hoffe, dies hat ein wenig zu diesem legendären Thema beigetragen. Ich habe mehrere gute Stunden damit verbracht, diesen Prozess zu zeichnen, um ihn zu verstehen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X