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

460voto

Douglas Mayle Punkte 19645

yield ist genau wie return - gibt er das zurück, was Sie ihm sagen (als Generator). Der Unterschied besteht darin, dass beim nächsten Aufruf des Generators die Ausführung mit dem letzten Aufruf des yield Erklärung. Im Gegensatz zur Rückkehr, Der Stack-Frame wird bei einem Yield nicht aufgeräumt, sondern die Kontrolle wird an den Aufrufer zurückgegeben, so dass sein Zustand beim nächsten Aufruf der Funktion wiederhergestellt wird.

Im Fall Ihres Codes ist die Funktion get_child_candidates verhält sich wie ein Iterator, so dass beim Erweitern der Liste ein Element nach dem anderen zur neuen Liste hinzugefügt wird.

list.extend ruft einen Iterator auf, bis er erschöpft ist. Im Fall des Codebeispiels, das Sie gepostet haben, wäre es viel klarer, einfach ein Tupel zurückzugeben und dieses an die Liste anzuhängen.

320voto

Claudiu Punkte 215027

Es gibt noch eine weitere Sache zu erwähnen: eine Funktion, die Ergebnisse liefert, muss nicht wirklich beendet werden. Ich habe Code wie diesen geschrieben:

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

Dann kann ich sie in anderem Code wie diesem verwenden:

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

Es hilft wirklich, einige Probleme zu vereinfachen und einige Dinge leichter zu handhaben.

311voto

Daniel Punkte 3116

Diejenigen, die ein minimales Arbeitsbeispiel bevorzugen, sollten sich diese interaktive Python-Sitzung zu Gemüte führen:

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

293voto

Bob Stein Punkte 14259

TL;DR

Stattdessen:

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

dies tun:

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

Immer, wenn Sie eine Liste von Grund auf neu aufbauen müssen, yield stattdessen jedes Stück.

Das war mein erster "Aha"-Moment mit dem Ertrag.


yield ist eine zuckerhaltig Art zu sagen

eine Reihe von Dingen bauen

Dasselbe Verhalten:

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

Unterschiedliches Verhalten:

Die Ausbeute ist Single-Pass : Sie können die Funktion nur einmal durchlaufen. Wenn eine Funktion einen Yield enthält, nennen wir sie eine Generatorfunktion . Und ein Iterator ist das, was es zurückgibt. Diese Begriffe sind aufschlussreich. Wir verlieren die Bequemlichkeit eines Containers, gewinnen aber die Leistung einer Reihe, die nach Bedarf berechnet wird und beliebig lang ist.

Die Ausbeute ist faule schiebt sie die Berechnung auf. Eine Funktion mit einer Ausbeute in ihr nicht wirklich ausgeführt wird, wenn Sie es aufrufen. Sie gibt eine Iterator-Objekt das sich daran erinnert, wo es aufgehört hat. Jedes Mal, wenn Sie aufrufen next() auf dem Iterator (dies geschieht in einer for-Schleife), wird die Ausführung zum nächsten Ergebnis fortgesetzt. return löst StopIteration aus und beendet die Reihe (dies ist das natürliche Ende einer for-Schleife).

Die Ausbeute ist vielseitig . Die Daten müssen nicht alle zusammen gespeichert werden, sondern können nach und nach verfügbar gemacht werden. Sie können unendlich sein.

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

Wenn Sie mehrere Durchgänge und die Serie ist nicht zu lang, rufen Sie einfach an list() darauf:

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

Hervorragende Wahl des Wortes yield denn beide Bedeutungen anwenden:

Ertrag - produzieren oder bereitstellen (wie in der Landwirtschaft)

...liefern die nächsten Daten der Reihe.

Ertrag - nachgeben oder aufgeben (wie bei politischer Macht)

...geben die CPU-Ausführung auf, bis der Iterator fortschreitet.

248voto

RBansal Punkte 2329

Die Rendite gibt Ihnen einen Generator.

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

Wie Sie sehen können, ist im ersten Fall foo hält die gesamte Liste auf einmal im Speicher. Bei einer Liste mit 5 Elementen ist das keine große Sache, aber was ist, wenn Sie eine Liste mit 5 Millionen Elementen haben wollen? Das ist nicht nur ein riesiger Speicherfresser, sondern kostet auch eine Menge Zeit, wenn die Funktion aufgerufen wird.

Im zweiten Fall, bar gibt Ihnen nur einen Generator. Ein Generator ist eine Iterable, was bedeutet, dass Sie ihn in einer for Schleife, usw., aber auf jeden Wert kann nur einmal zugegriffen werden. Alle Werte werden auch nicht gleichzeitig im Speicher abgelegt; das Generatorobjekt "merkt" sich, wo es sich in der Schleife befand, als Sie es das letzte Mal aufriefen - auf diese Weise müssen Sie, wenn Sie eine Iterable verwenden, um (sagen wir) bis 50 Milliarden zu zählen, nicht auf einmal bis 50 Milliarden zählen und die 50 Milliarden Zahlen zum Durchzählen speichern.

Auch dies ist ein ziemlich erfundenes Beispiel, Sie würden wahrscheinlich itertools verwenden, wenn Sie wirklich bis 50 Milliarden zählen wollten :)

Dies ist der einfachste Anwendungsfall von Generatoren. Wie Sie schon sagten, kann man damit effiziente Permutationen schreiben, indem man Yield verwendet, um Dinge über den Call Stack nach oben zu schieben, anstatt eine Art Stack-Variable zu verwenden. Generatoren können auch für spezialisierte Tree Traversal und alle Arten von anderen Dingen verwendet werden.

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