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

247voto

Jon Skeet Punkte 1325502

Es gibt einen Generator zurück. Ich bin nicht besonders vertraut mit Python, aber ich glaube, es ist die gleiche Art von Sache als C#s Iterator-Blöcke falls Sie damit vertraut sind.

Der Kerngedanke ist, dass der Compiler/Interpreter/Was-auch-immer etwas trickst, damit der Aufrufer next() immer wieder aufrufen kann und es immer wieder Werte zurückgibt - als ob die Generatormethode pausiert wäre . Da man eine Methode nicht wirklich "anhalten" kann, erstellt der Compiler einen Zustandsautomaten, damit man weiß, wo man sich gerade befindet und wie die lokalen Variablen usw. aussehen. Das ist viel einfacher, als selbst einen Iterator zu schreiben.

221voto

tzot Punkte 86792

Hier ist ein Beispiel in einfacher Sprache. Ich werde eine Korrespondenz zwischen hochrangigen menschlichen Konzepten und niedrigrangigen Python-Konzepten herstellen.

Ich möchte eine Zahlenfolge bearbeiten, aber ich möchte mich nicht mit der Erstellung dieser Folge beschäftigen, sondern mich nur auf die Operation konzentrieren, die ich durchführen möchte. Also mache ich Folgendes:

  • Ich rufe Sie an und sage Ihnen, dass ich eine Zahlenfolge möchte, die auf eine bestimmte Weise berechnet wird, und ich teile Ihnen mit, wie der Algorithmus aussieht.
    Dieser Schritt entspricht def der Generatorfunktion, d. h. der Funktion, die eine yield .
  • Irgendwann später sage ich zu Ihnen: "OK, machen Sie sich bereit, mir die Zahlenfolge zu nennen".
    Dieser Schritt entspricht dem Aufruf der Generatorfunktion, die ein Generatorobjekt zurückgibt. Beachten Sie, dass Sie mir noch keine Zahlen nennen, sondern nur Papier und Bleistift in die Hand nehmen.
  • Ich frage dich: "Sag mir die nächste Zahl", und du sagst mir die erste Zahl; danach wartest du, bis ich dich nach der nächsten Zahl frage. Es ist Ihre Aufgabe, sich zu merken, wo Sie waren, welche Zahlen Sie bereits gesagt haben und wie die nächste Zahl lautet. Ich interessiere mich nicht für die Details.
    Dieser Schritt entspricht dem Aufruf von next(generator) auf das Generatorobjekt.
    (In Python 2, .next war eine Methode des Generatorobjekts; in Python 3 heißt sie .__next__ aufzurufen, aber der richtige Weg ist die Verwendung des eingebauten next() Funktion genau wie len() y .__len__ )
  • vorherigen Schritt wiederholen, bis
  • Irgendwann könnte es zu einem Ende kommen. Du nennst mir keine Zahl, sondern rufst nur: "Immer mit der Ruhe! Ich bin fertig! Keine Zahlen mehr!"
    Dieser Schritt entspricht dem Beenden der Arbeit des Generatorobjekts und dem Auslösen einer StopIteration Ausnahme.
    Die Generatorfunktion muss die Ausnahme nicht auslösen. Sie wird automatisch ausgelöst, wenn die Funktion endet oder eine return .

Dies ist die Aufgabe eines Generators (eine Funktion, die eine yield ); es beginnt mit der Ausführung bei der ersten next() pausiert, wenn es eine yield und auf die Frage nach dem next() Wert geht es an der Stelle weiter, an der es zuletzt war. Es passt vom Design her perfekt zum Iterator-Protokoll von Python, das beschreibt, wie man Werte sequentiell anfordert.

Der bekannteste Anwender des Iterator-Protokolls ist das for Befehl in Python. Also, wann immer Sie einen:

for item in sequence:

Es spielt keine Rolle, ob sequence eine Liste, eine Zeichenkette, ein Wörterbuch oder ein Generator ist Objekt wie oben beschrieben; das Ergebnis ist dasselbe: Sie lesen die Elemente einer Sequenz eines nach dem anderen ab.

Beachten Sie, dass def einer Funktion, die eine yield ist nicht die einzige Möglichkeit, einen Generator zu erstellen; es ist nur der einfachste Weg, einen zu erstellen.

Für genauere Informationen lesen Sie bitte über Iterator-Typen le Ertragsanweisung y Stromerzeuger in der Python-Dokumentation.

219voto

aestrivex Punkte 4880

Es gibt eine Art von Antwort, die meiner Meinung nach noch nicht gegeben wurde, unter den vielen großartigen Antworten, die beschreiben, wie man Generatoren verwendet. Hier ist die programmiersprachtheoretische Antwort:

Le site yield Anweisung in Python gibt einen Generator zurück. Ein Generator in Python ist eine Funktion, die Folgendes zurückgibt Fortsetzungen (und speziell eine Art von Coroutine, aber Fortsetzungen stellen den allgemeineren Mechanismus dar, um zu verstehen, was vor sich geht).

Fortsetzungen in der Programmiersprachentheorie sind eine viel grundlegendere Art der Berechnung, werden aber nicht oft verwendet, weil sie extrem schwer zu begründen und auch sehr schwer zu implementieren sind. Aber die Idee, was eine Fortsetzung ist, ist einfach: Es ist der Zustand einer Berechnung, die noch nicht beendet ist. In diesem Zustand werden die aktuellen Werte der Variablen, die noch auszuführenden Operationen usw. gespeichert. Zu einem späteren Zeitpunkt im Programm kann die Fortsetzung dann aufgerufen werden, so dass die Variablen des Programms in diesen Zustand zurückgesetzt und die gespeicherten Operationen ausgeführt werden.

Fortsetzungen, in dieser allgemeineren Form, können auf zwei Arten implementiert werden. In der call/cc Auf diese Weise wird der Stack des Programms buchstäblich gespeichert, und wenn die Fortsetzung aufgerufen wird, wird der Stack wiederhergestellt.

Im Continuation-Passing-Stil (CPS) sind Fortsetzungen ganz normale Funktionen (nur in Sprachen, in denen Funktionen erste Klasse sind), die der Programmierer explizit verwaltet und an Unterprogramme weitergibt. In diesem Stil wird der Programmzustand durch Closures (und die darin kodierten Variablen) dargestellt und nicht durch Variablen, die sich irgendwo auf dem Stack befinden. Funktionen, die den Kontrollfluss verwalten, akzeptieren Fortsetzungen als Argumente (in einigen Varianten von CPS können Funktionen mehrere Fortsetzungen akzeptieren) und manipulieren den Kontrollfluss, indem sie einfach aufgerufen werden und anschließend zurückkehren. Ein sehr einfaches Beispiel für die Weitergabe von Fortsetzungen ist das folgende:

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 diesem (sehr vereinfachten) Beispiel speichert der Programmierer den Vorgang des eigentlichen Schreibens der Datei in einer Fortsetzung (was potenziell ein sehr komplexer Vorgang mit vielen auszuschreibenden Details sein kann) und übergibt dann diese Fortsetzung (d. h. als erstklassige Schließung) an einen anderen Operator, der weitere Verarbeitungsschritte durchführt und sie dann bei Bedarf aufruft. (Ich verwende dieses Entwurfsmuster häufig in der GUI-Programmierung, entweder weil es mir Codezeilen spart oder, was noch wichtiger ist, um den Kontrollfluss nach dem Auslösen von GUI-Ereignissen zu verwalten).

Im weiteren Verlauf dieses Beitrags werden Fortsetzungen als CPS konzeptualisiert, da dies sehr viel einfacher zu verstehen und zu lesen ist.

Lassen Sie uns nun über Generatoren in Python sprechen. Generatoren sind ein spezieller Untertyp von Fortsetzungen. Während Fortsetzungen sind im Allgemeinen in der Lage, den Zustand einer Berechnung (d.h. der Aufrufstapel des Programms), Generatoren sind nur in der Lage, den Zustand der Iteration über eine Iterator . Allerdings ist diese Definition für bestimmte Anwendungsfälle von Generatoren etwas irreführend. Zum Beispiel:

def f():
  while True:
    yield 4

Dies ist eindeutig eine vernünftige Iterable, deren Verhalten gut definiert ist - jedes Mal, wenn der Generator darüber iteriert, gibt er 4 zurück (und tut dies für immer). Aber es ist wahrscheinlich nicht der prototypische Typ von Iterablen, der einem in den Sinn kommt, wenn man an Iteratoren denkt (d.h., for x in collection: do_something(x) ). Dieses Beispiel verdeutlicht die Leistungsfähigkeit von Generatoren: Wenn etwas ein Iterator ist, kann ein Generator den Zustand seiner Iteration speichern.

Um es noch einmal zu wiederholen: Fortsetzungen können den Zustand des Stapels eines Programms speichern, während Generatoren den Zustand der Iteration speichern können. Das bedeutet, dass Fortsetzungen sehr viel leistungsfähiger sind als Generatoren, aber auch, dass Generatoren viel, viel einfacher sind. Sie sind für den Sprachdesigner einfacher zu implementieren und für den Programmierer einfacher zu benutzen (wenn Sie etwas Zeit haben, lesen und verstehen Sie diese Seite über Fortsetzungen und call/cc ).

Aber man könnte Generatoren leicht als einen einfachen, spezifischen Fall von Continuation-Passing-Stil implementieren (und konzeptualisieren):

Wann immer yield aufgerufen wird, teilt sie der Funktion mit, dass sie eine Fortsetzung zurückgeben soll. Wenn die Funktion erneut aufgerufen wird, beginnt sie dort, wo sie aufgehört hat. In Pseudo-Pseudocode (d. h. nicht Pseudocode, aber auch nicht Code) ist der Generator also next Methode ist im Wesentlichen wie folgt:

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

wo die yield Schlüsselwort ist eigentlich syntaktischer Zucker für die eigentliche Generatorfunktion, im Grunde etwas wie:

def generatorfun(iterable):
  if len(iterable) == 0:
    raise StopIteration
  else:
    return (iterable[0], lambda:generatorfun(iterable[1:]))

Denken Sie daran, dass dies nur Pseudocode ist und die tatsächliche Implementierung von Generatoren in Python komplexer ist. Aber als Übung, um zu verstehen, was vor sich geht, versuchen Sie, Generatorobjekte im Stil der Weitergabe von Fortsetzungen zu implementieren, ohne die yield Stichwort.

171voto

Mike McKerns Punkte 30236

Während viele Antworten zeigen, warum man eine yield um einen Generator zu erstellen, gibt es weitere Verwendungsmöglichkeiten für yield . Es ist recht einfach, eine Coroutine zu erstellen, die die Weitergabe von Informationen zwischen zwei Codeblöcken ermöglicht. Ich werde keine der schönen Beispiele wiederholen, die bereits über die Verwendung von yield um einen Generator zu erstellen.

Um zu verstehen, was ein yield im folgenden Code, können Sie mit dem Finger den Zyklus durch jeden Code verfolgen, der eine yield . Jedes Mal, wenn Ihr Finger auf die yield müssen Sie auf eine next oder eine send eingegeben werden. Wenn eine next aufgerufen wird, gehen Sie durch den Code, bis Sie auf die yield der Code auf der rechten Seite des Feldes yield wird ausgewertet und an den Aufrufer zurückgegeben dann warten Sie. Wenn next erneut aufgerufen wird, führen Sie eine weitere Schleife durch den Code aus. Sie werden jedoch feststellen, dass in einer Coroutine, yield kann auch mit einer send der einen Wert vom Anrufer sendet in die nachgiebige Funktion. Wenn eine send gegeben ist, dann yield empfängt den gesendeten Wert und spuckt ihn auf der linken Seite aus dann geht die Verfolgung durch den Code weiter, bis man auf die yield wieder (wobei der Wert am Ende zurückgegeben wird, als ob next aufgerufen wurde).

Zum Beispiel:

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

167voto

Sławomir Lenart Punkte 6134

Es gibt eine weitere yield Verwendung und Bedeutung (seit Python 3.3):

yield from <expr>

Von PEP 380 -- Syntax für das Delegieren an einen Untergenerator :

Es wird eine Syntax vorgeschlagen, nach der ein Generator einen Teil seiner Operationen an einen anderen Generator delegieren kann. Dadurch kann ein Codeabschnitt, der "yield" enthält, ausgegliedert und in einem anderen Generator platziert werden. Außerdem kann der Subgenerator mit einem Wert zurückkehren, der dem delegierenden Generator zur Verfügung gestellt wird.

Die neue Syntax eröffnet auch einige Optimierungsmöglichkeiten, wenn ein Generator Werte wiedergibt, die von einem anderen erzeugt wurden.

Außerdem diese einführen wird (seit Python 3.5):

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

um zu vermeiden, dass Koroutinen mit einem regulären Generator verwechselt werden (heute yield wird in beiden verwendet).

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