Kann mir jemand ein Beispiel geben, warum die mit der Python-Generatorfunktion verknüpfte "Sende" -Funktion existiert? Ich verstehe die Ertragsfunktion vollständig. Die Sendefunktion ist für mich jedoch verwirrend. Die Dokumentation zu dieser Methode ist kompliziert:
generator.send(value)
Setzt die Ausführung fort und "sendet" einen Wert an die Generatorfunktion. Das Wertargument wird zum Ergebnis des aktuellen Ertragsausdrucks. Die send () -Methode gibt den nächsten vom Generator ausgegebenen Wert zurück oder löst StopIteration aus, wenn der Generator beendet wird, ohne einen anderen Wert zu liefern.
Was bedeutet das? Ich dachte, Wert war die Eingabe für die Funktion? Der Ausdruck "Die send () -Methode gibt den nächsten vom Generator ausgegebenen Wert zurück" scheint auch der genaue Zweck der Ertragsfunktion zu sein. Yield gibt den nächsten Wert zurück, den der Generator liefert ...
Kann mir jemand ein Beispiel für einen Generator geben, der send verwendet und etwas erreicht, was nicht möglich ist?
send()
aufgerufen wird, um den Generator zu starten, muss erNone
als Argument aufgerufen werden , da es keinen Ertragsausdruck gibt, der den Wert erhalten könnte.", Zitiert aus dem offiziellen Dokument und für das das Zitat in der Frage ist fehlt.Antworten:
Es wird verwendet, um Werte an einen Generator zu senden, der gerade nachgegeben hat. Hier ist ein künstliches (nicht nützliches) Erklärungsbeispiel:
Sie können dies nicht nur mit tun
yield
.Einer der besten Anwendungsfälle, die ich je gesehen habe, ist Twisted's
@defer.inlineCallbacks
. Im Wesentlichen können Sie damit eine Funktion wie die folgende schreiben:Was passiert , ist , dass die
takesTwoSeconds()
Erträge einDeferred
, der ein Wert ist viel versprechend Wert wird , wird später berechnet werden. Twisted kann die Berechnung in einem anderen Thread ausführen. Wenn die Berechnung abgeschlossen ist, wird sie an den verzögerten Wert übergeben, und der Wert wird dann an diedoStuff()
Funktion zurückgesendet. SomitdoStuff()
kann das mehr oder weniger wie eine normale prozedurale Funktion aussehen, außer dass es alle Arten von Berechnungen und Rückrufen usw. ausführen kann. Die Alternative vor dieser Funktionalität wäre, Folgendes zu tun:Es ist viel komplizierter und unhandlicher.
quelle
Diese Funktion dient zum Schreiben von Coroutinen
druckt
Sehen Sie, wie die Steuerung hin und her geleitet wird? Das sind Coroutinen. Sie können für alle Arten von coolen Dingen wie Asynch IO und ähnlichem verwendet werden.
Stellen Sie sich das so vor, mit einem Generator und ohne Senden ist es eine Einbahnstraße
Aber mit send wird es eine Einbahnstraße
Die die Tür für den Benutzer das Anpassen der Generatoren Verhalten eröffnet im Fluge und den Generator für den Benutzer reagiert.
quelle
send()
der Generator beim ersten Aufruf das Schlüsselwortyield
noch nicht erreicht hat.Dies kann jemandem helfen. Hier ist ein Generator, der von der Sendefunktion nicht betroffen ist. Es nimmt den Parameter number bei der Instanziierung auf und bleibt von send unberührt:
Hier ist, wie Sie dieselbe Art von Funktion mit send ausführen würden, sodass Sie bei jeder Iteration den Wert von number ändern können:
So sieht das aus, da das Senden eines neuen Werts für die Zahl das Ergebnis ändert:
Sie können dies auch als solche in eine for-Schleife einfügen:
Weitere Hilfe finden Sie in diesem großartigen Tutorial .
quelle
send
? Ein Einfacherlambda x: x * 2
macht dasselbe auf eine viel weniger verworrene Weise.Einige Anwendungsfälle für die Verwendung von Generator und
send()
Generatoren mit
send()
erlauben:Hier sind einige Anwendungsfälle:
Beobachteter Versuch, einem Rezept zu folgen
Lassen Sie uns ein Rezept haben, das vordefinierte Eingaben in einer bestimmten Reihenfolge erwartet.
Wir können:
watched_attempt
Instanz aus dem RezeptÜberprüfen Sie bei jeder Eingabe, ob die Eingabe die erwartete ist (und schlagen Sie fehl, wenn dies nicht der Fall ist).
Um es zu verwenden, erstellen Sie zuerst die
watched_attempt
Instanz:Der Aufruf von
.next()
ist erforderlich, um die Ausführung des Generators zu starten.Der zurückgegebene Wert zeigt an, dass unser Topf derzeit leer ist.
Führen Sie nun einige Aktionen aus, die den Erwartungen des Rezepts entsprechen:
Wie wir sehen, ist der Topf endlich leer.
Falls man das Rezept nicht befolgen würde, würde es scheitern (was das gewünschte Ergebnis eines beobachteten Kochversuchs sein könnte - nur zu lernen, dass wir nicht genug Aufmerksamkeit geschenkt haben, als wir Anweisungen erhielten.
Beachte das:
Laufende Summen
Wir können den Generator verwenden, um die laufende Summe der an ihn gesendeten Werte zu verfolgen.
Jedes Mal, wenn wir eine Zahl hinzufügen, wird die Anzahl der Eingaben und die Gesamtsumme zurückgegeben (gültig für den Moment, in dem die vorherige Eingabe gesendet wurde).
Die Ausgabe würde folgendermaßen aussehen:
quelle
Die
send()
Methode steuert den Wert links vom Ertragsausdruck.Um zu verstehen, wie sich die Ausbeute unterscheidet und welchen Wert sie hat, aktualisieren wir zunächst schnell die Reihenfolge, in der der Python-Code ausgewertet wird.
Abschnitt 6.15 Auswertungsreihenfolge
Ein Ausdruck auf
a = b
der rechten Seite wird also zuerst ausgewertet.Wie das Folgende zeigt, wird
a[p('left')] = p('right')
die rechte Seite zuerst ausgewertet.Was macht Yield?, Yield, setzt die Ausführung der Funktion aus und kehrt zum Aufrufer zurück und setzt die Ausführung an der Stelle fort, an der sie vor dem Suspendieren aufgehört hat.
Wo genau wird die Ausführung ausgesetzt? Sie haben es vielleicht schon erraten ... die Ausführung wird zwischen der rechten und der linken Seite des Ertragsausdrucks angehalten. Daher wird
new_val = yield old_val
die Ausführung am=
Vorzeichen angehalten , und der Wert auf der rechten Seite (der vor dem Anhalten liegt und auch der an den Aufrufer zurückgegebene Wert ist) kann etwas anderes sein als der Wert auf der linken Seite (der Wert, der nach der Wiederaufnahme zugewiesen wird Ausführung).yield
ergibt 2 Werte, einen rechts und einen links.Wie steuern Sie den Wert auf der linken Seite des Ertragsausdrucks? über die
.send()
Methode.6.2.9. Ertragsausdrücke
quelle
Die
send
Methode implementiert Coroutinen .Wenn Sie Coroutines noch nicht kennengelernt haben, ist es schwierig, den Kopf herumzureißen, da sie die Art und Weise ändern, wie ein Programm abläuft. Sie können ein gutes Tutorial für weitere Details lesen .
quelle
Das Wort "Ertrag" hat zwei Bedeutungen: etwas zu produzieren (z. B. Mais zu liefern) und anzuhalten, um jemanden / etwas anderes weiterlaufen zu lassen (z. B. Autos, die Fußgängern nachgeben). Beide Definitionen gelten für das
yield
Schlüsselwort von Python . Das Besondere an Generatorfunktionen ist, dass im Gegensatz zu regulären Funktionen Werte an den Aufrufer "zurückgegeben" werden können, während eine Generatorfunktion lediglich angehalten und nicht beendet wird.Es ist am einfachsten, sich einen Generator als ein Ende eines bidirektionalen Rohrs mit einem "linken" Ende und einem "rechten" Ende vorzustellen. Diese Pipe ist das Medium, über das Werte zwischen dem Generator selbst und dem Körper der Generatorfunktion gesendet werden. Jedes Ende der Pipe hat zwei Operationen: Diese
push
sendet einen Wert und blockiert, bis das andere Ende der Pipe den Wert abruft und nichts zurückgibt. undpull
, der blockiert, bis das andere Ende der Pipe einen Wert drückt, und den gedrückten Wert zurückgibt. Zur Laufzeit springt die Ausführung zwischen den Kontexten auf beiden Seiten der Pipe hin und her. Jede Seite wird ausgeführt, bis ein Wert an die andere Seite gesendet wird. An diesem Punkt wird sie angehalten, die andere Seite wird ausgeführt und wartet auf einen Wert in Rückkehr, an diesem Punkt hält die andere Seite an und es wird fortgesetzt. Mit anderen Worten, jedes Ende der Pipe verläuft von dem Moment, in dem es einen Wert empfängt, bis zu dem Moment, in dem es einen Wert sendet.Das Rohr ist funktional symmetrisch, aber - vereinbarungs ich in dieser Antwort zu definieren - das linke Ende innerhalb der Generatorfunktion des Körpers nur verfügbar ist , und ist über das
yield
Schlüsselwort, während das rechte Ende ist der Generator und ist über die Generatorfunktionsend
. Als singuläre Schnittstellen zu ihren jeweiligen Rohrendenyield
und mitsend
doppelter Aufgabe: Sie drücken und ziehen jeweils Werte zu / von ihren Rohrenden,yield
drücken nach rechts und ziehen nach links, währendsend
sie das Gegenteil tun . Diese doppelte Pflicht ist der Kern der Verwirrung um die Semantik von Aussagen wiex = yield y
. Brechenyield
undsend
in zwei expliziten Push / Pull - Schritte wird ihre Semantik viel klarer machen:g
ist der Generator.g.send
schiebt einen Wert nach links durch das rechte Ende des Rohrs.g
Pausen, damit der Körper der Generatorfunktion ausgeführt werden kann.g.send
durchgeschobene Wert wird nach links gezogenyield
und am linken Ende des Rohrs empfangen. Inx = yield y
,x
wird dem gezogen Wert zugeordnet.yield
erreicht ist.yield
schiebt einen Wert nach rechts durch das linke Ende des Rohrs zurück nachg.send
. Inx = yield y
,y
ist nach rechts durch das Rohr geschoben wird .g.send
setzt den Wert fort, zieht ihn ab und gibt ihn an den Benutzer zurück.g.send
das nächste Mal aufgerufen werden, kehren Sie zu Schritt 1 zurück.Während zyklisch, hat diese Prozedur einen Anfang: wann
g.send(None)
- wasnext(g)
kurz ist - zum ersten Mal aufgerufen wird (es ist illegal, etwas anderes alsNone
den erstensend
Aufruf zu übergeben). Und es kann ein Ende haben: Wennyield
im Körper der Generatorfunktion keine weiteren Aussagen mehr zu erreichen sind.Sehen Sie, was die
yield
Aussage (oder genauer gesagt die Generatoren) so besonders macht? Im Gegensatz zum dürftigenreturn
Schlüsselwort kannyield
es Werte an seinen Aufrufer übergeben und Werte von seinem Aufrufer empfangen, ohne die Funktion zu beenden, in der es lebt! (Wenn Sie eine Funktion - oder einen Generator - beenden möchten, ist es natürlich praktisch, auch dasreturn
Schlüsselwort zu haben .) Wenn eineyield
Anweisung gefunden wird, wird die Generatorfunktion lediglich angehalten und dann genau dort wieder aufgenommen, wo sie übrig geblieben ist aus, wenn ein anderer Wert gesendet wird. Undsend
ist nur die Schnittstelle für die Kommunikation mit dem Inneren einer Generatorfunktion von außerhalb.Wenn wir wirklich diese Push / Pull / Rohr Analogie so weit wie möglich brechen wollen, haben wir am Ende mit dem folgenden Pseudo - Code auf das wirklich nach Hause fährt , dass, abgesehen von den Schritten 1-5,
yield
undsend
sind zwei Seiten der gleichenMünzeRohr:right_end.push(None) # the first half of g.send; sending None is what starts a generator
right_end.pause()
left_end.start()
initial_value = left_end.pull()
if initial_value is not None: raise TypeError("can't send non-None value to a just-started generator")
left_end.do_stuff()
left_end.push(y) # the first half of yield
left_end.pause()
right_end.resume()
value1 = right_end.pull() # the second half of g.send
right_end.do_stuff()
right_end.push(value2) # the first half of g.send (again, but with a different value)
right_end.pause()
left_end.resume()
x = left_end.pull() # the second half of yield
goto 6
Der Schlüssel Transformation besteht darin , dass wir uns getrennt haben
x = yield y
undvalue1 = g.send(value2)
jeweils in zwei Aussagen:left_end.push(y)
undx = left_end.pull()
; undvalue1 = right_end.pull()
undright_end.push(value2)
. Es gibt zwei Sonderfälle desyield
Schlüsselworts:x = yield
undyield y
. Dies sind syntaktische Zucker fürx = yield None
und_ = yield y # discarding value
.Spezifische Details bezüglich der genauen Reihenfolge, in der Werte durch das Rohr gesendet werden, finden Sie unten.
Was folgt, ist ein ziemlich langes konkretes Modell des Obigen. Erstens, es sollte zuerst angemerkt werden , dass für jeden Generator
g
,next(g)
ist genau äquivalent zug.send(None)
. In diesem Sinne können wir uns nur darauf konzentrieren, wie essend
funktioniert, und nur über die Weiterentwicklung des Generators sprechensend
.Angenommen, wir haben
Nun die Definition von
f
ungefähr Desugars für die folgende gewöhnliche (Nicht-Generator-) Funktion:Bei dieser Transformation von ist Folgendes passiert
f
:left_end
auf die von der verschachtelten Funktionright_end
zugegriffen wird und auf die vom äußeren Bereich zurückgegeben und zugegriffen wird - dasright_end
ist das, was wir als Generatorobjekt kennen.left_end.pull()
istNone
, einen geschoben Wert im Prozess verbrauchen.x = yield y
durch zwei Zeilen ersetzt:left_end.push(y)
undx = left_end.pull()
.send
Funktion für definiertright_end
, die das Gegenstück zu den beiden Zeilen ist, durch die wir diex = yield y
Anweisung im vorherigen Schritt ersetzt haben.In dieser Fantasiewelt, in der Funktionen nach der Rückkehr fortgesetzt werden können,
g
werden sie zugewiesenright_end
und dannimpl()
aufgerufen. Wenn wir also in unserem obigen Beispiel die Ausführung Zeile für Zeile verfolgen würden, würde ungefähr Folgendes passieren:Dies entspricht genau dem obigen 16-Stufen-Pseudocode.
Es gibt einige andere Details, wie z. B. wie sich Fehler ausbreiten und was passiert, wenn Sie das Ende des Generators erreichen (das Rohr ist geschlossen), aber dies sollte klar machen, wie der grundlegende Steuerungsfluss funktioniert, wenn er
send
verwendet wird.Schauen wir uns mit denselben Desugaring-Regeln zwei Sonderfälle an:
Zum größten Teil desugar sie auf die gleiche Weise wie
f
, die einzigen Unterschiede sind, wie dieyield
Aussagen transformiert werden:Im ersten Fall wurde der Wert an übergeben
f1
verschoben (nachgegeben), und dann werden alle gezogenen (gesendeten) Werte direkt zurückgeschoben (nachgegeben). Im zweiten Fallx
hat (noch) keinen Wert, wenn es zum ersten Mal dazu kommtpush
, also wird einUnboundLocalError
erhöht.quelle
yield
.send
; Es dauert einen Aufruf vonsend(None)
, um den Cursor auf die ersteyield
Anweisung zu bewegen , und erst dannsend
senden nachfolgende Aufrufe tatsächlich einen "echten" Wert anyield
.f
irgendwann funktioniertyield
, und also warten, bis er einesend
vom Anrufer erhält ? Mit einer normalen Funktion würde der Interpreter sofort mit der Ausführung beginnenf
, oder? Schließlich gibt es in Python keinerlei AOT-Kompilierung. Sind Sie sicher, dass dies der Fall ist? (ohne zu hinterfragen, was Sie sagen, ich bin wirklich nur verwirrt über das, was Sie hier geschrieben haben). Wo kann ich mehr darüber lesen, wie Python weiß, dass es warten muss, bevor es den Rest der Funktion ausführt?send(None)
den entsprechenden Wert (z. B.1
) ergibt, ohne sieNone
an den Generator zu senden , legt jedoch nahe, dass der erste Aufruf vonsend
ein Sonderfall ist. Es ist eine schwierige Oberfläche zum Entwerfen; Wenn Sie dem erstensend
einen beliebigen Wert senden lassen , ist die Reihenfolge der ermittelten Werte und der gesendeten Werte im Vergleich zu der aktuellen um eins verschoben.Diese verwirrten mich auch. Hier ist ein Beispiel, das ich gemacht habe, als ich versucht habe, einen Generator einzurichten, der Signale in abwechselnder Reihenfolge liefert und akzeptiert (Ausbeute, Akzeptieren, Ausbeute, Akzeptieren) ...
Die Ausgabe ist:
quelle