Es fällt mir schwer, mein Gehirn um PEP 380 zu wickeln .
- In welchen Situationen ist "Ertrag aus" sinnvoll?
- Was ist der klassische Anwendungsfall?
- Warum wird es mit Mikrofäden verglichen?
[Update]
Jetzt verstehe ich die Ursache meiner Schwierigkeiten. Ich habe Generatoren verwendet, aber nie wirklich Coroutinen (eingeführt von PEP-342 ). Trotz einiger Ähnlichkeiten sind Generatoren und Coroutinen grundsätzlich zwei verschiedene Konzepte. Das Verständnis der Coroutinen (nicht nur der Generatoren) ist der Schlüssel zum Verständnis der neuen Syntax.
IMHO Coroutinen sind die dunkelste Python-Funktion , die meisten Bücher lassen es nutzlos und uninteressant aussehen.
Vielen Dank für die tollen Antworten, aber besonderen Dank an agf und seinen Kommentar zu den Präsentationen von David Beazley . David rockt.
Antworten:
Lassen Sie uns zuerst eines aus dem Weg räumen. Die Erklärung,
yield from g
die gleichbedeutend ist,for v in g: yield v
wird dem , worumyield from
es geht, nicht einmal gerecht . Seien wir ehrlich, wenn nuryield from
diefor
Schleife erweitert wird, ist es nicht gerechtfertigtyield from
, die Sprache zu erweitern und eine ganze Reihe neuer Funktionen in Python 2.x nicht zu implementieren.Dadurch
yield from
wird eine transparente bidirektionale Verbindung zwischen dem Anrufer und dem Subgenerator hergestellt :Die Verbindung ist "transparent" in dem Sinne, dass sie auch alles korrekt weitergibt, nicht nur die generierten Elemente (z. B. Ausnahmen werden weitergegeben).
Die Verbindung ist "bidirektional" in dem Sinne, dass Daten sowohl von als auch zu einem Generator gesendet werden können.
( Wenn wir über TCP sprechen,
yield from g
könnte dies bedeuten, dass "der Socket meines Clients vorübergehend getrennt und wieder mit diesem anderen Server-Socket verbunden wird". )Übrigens, wenn Sie nicht sicher sind, was das Senden von Daten an einen Generator überhaupt bedeutet, müssen Sie zuerst alles löschen und über Coroutinen lesen - sie sind sehr nützlich (im Gegensatz zu Unterprogrammen ), aber in Python leider weniger bekannt. Dave Beazleys Curious Course on Coroutines ist ein ausgezeichneter Start. Lesen Sie die Folien 24-33 für eine schnelle Grundierung.
Lesen von Daten aus einem Generator mit Ertrag aus
Anstatt manuell zu iterieren
reader()
, können wir es einfach tunyield from
.Das funktioniert und wir haben eine Codezeile entfernt. Und wahrscheinlich ist die Absicht etwas klarer (oder nicht). Aber nichts, was das Leben verändert.
Senden von Daten an einen Generator (Coroutine) unter Verwendung der Ausbeute aus - Teil 1
Jetzt machen wir etwas interessanteres. Erstellen wir eine Coroutine namens
writer
, die an sie gesendete Daten akzeptiert und in einen Socket, fd usw. schreibt.Die Frage ist nun, wie die Wrapper-Funktion das Senden von Daten an den Writer behandeln soll, damit alle Daten, die an den Wrapper gesendet werden, transparent an den Wrapper gesendet werden
writer()
.Der Wrapper muss die an ihn gesendeten Daten akzeptieren (offensichtlich) und sollte auch das verarbeiten,
StopIteration
wenn die for-Schleife erschöpft ist. Offensichtlich reichtfor x in coro: yield x
es nicht, nur etwas zu tun. Hier ist eine Version, die funktioniert.Oder wir könnten das tun.
Das spart 6 Codezeilen, macht es viel lesbarer und es funktioniert einfach. Magie!
Das Senden von Daten an einen Generator ergibt sich aus - Teil 2 - Ausnahmebehandlung
Machen wir es komplizierter. Was ist, wenn unser Autor Ausnahmen behandeln muss? Nehmen wir an, die
writer
Griffe a werdenSpamException
gedruckt,***
wenn sie auf einen treffen.Was ist, wenn wir uns nicht ändern
writer_wrapper
? Funktioniert es? Lass es uns versuchenÄhm, es funktioniert nicht, weil
x = (yield)
nur die Ausnahme ausgelöst wird und alles zum Stillstand kommt. Lassen Sie es funktionieren, aber behandeln Sie Ausnahmen manuell und senden Sie sie oder werfen Sie sie in den Subgenerator (writer
)Das funktioniert.
Aber das tut es auch!
Das
yield from
transparent behandelt das Senden der Werte oder das Werfen von Werten in den Subgenerator.Dies deckt jedoch immer noch nicht alle Eckfälle ab. Was passiert, wenn der äußere Generator geschlossen ist? Was ist mit dem Fall, wenn der Untergenerator einen Wert zurückgibt (ja, in Python 3.3+ können Generatoren Werte zurückgeben), wie sollte der Rückgabewert weitergegeben werden? Das
yield from
transparente Handling aller Eckkoffer ist wirklich beeindruckend .yield from
funktioniert einfach magisch und behandelt all diese Fälle.Ich persönlich halte dies
yield from
für eine schlechte Keyword-Wahl, da dadurch die wechselseitige Natur nicht erkennbar wird. Es wurden andere Schlüsselwörter vorgeschlagen (wiedelegate
aber abgelehnt, da das Hinzufügen eines neuen Schlüsselworts zur Sprache viel schwieriger ist als das Kombinieren vorhandener.Zusammengefasst ist es am besten zu denken ,
yield from
alstransparent two way channel
zwischen dem Anrufer und dem Teilgenerator.Verweise:
quelle
except StopIteration: pass
INSIDE diewhile True:
Schleife ist nicht eine genaue Darstellungyield from coro
- die nicht eine Endlosschleife ist und nachcoro
erschöpft ist (dh hebt StopIteration),writer_wrapper
wird die nächste Anweisung ausführen. Nach der letzten Aussage wird es sich automatischStopIteration
wie jeder erschöpfte Generator erhöhen ...writer
enthaltenfor _ in range(4)
stattwhile True
, dann würde es nach dem Drucken>> 3
AUCH automatisch angehobenStopIteration
und dies würde automatisch von behandeltyield from
und dannwriter_wrapper
automatisch angehobenStopIteration
und dawrap.send(i)
es sich nicht innerhalb destry
Blocks befindet, würde es an diesem Punkt tatsächlich ausgelöst werden ( dh Traceback wird nur die Leitung mit meldenwrap.send(i)
, nichts aus dem Inneren des Generators)Jede Situation, in der Sie eine Schleife wie diese haben:
Wie der PEP beschreibt, ist dies ein eher naiver Versuch, den Subgenerator zu verwenden. Es fehlen einige Aspekte, insbesondere die ordnungsgemäße Handhabung der von PEP 342 eingeführten
.throw()
/.send()
/.close()
Mechanismen . Um dies richtig zu machen, ist ziemlich komplizierter Code notwendig.Beachten Sie, dass Sie Informationen aus einer rekursiven Datenstruktur extrahieren möchten. Angenommen, wir möchten alle Blattknoten in einem Baum erhalten:
Noch wichtiger ist die Tatsache, dass es bis
yield from
dahin keine einfache Methode gab, den Generatorcode umzugestalten. Angenommen, Sie haben einen (sinnlosen) Generator wie diesen:Jetzt entscheiden Sie sich, diese Schleifen in separate Generatoren zu zerlegen. Ohne
yield from
ist dies hässlich, bis zu dem Punkt, an dem Sie zweimal überlegen werden, ob Sie es tatsächlich tun möchten. Mityield from
ist es eigentlich schön anzusehen:Ich denke, in diesem Abschnitt des PEP geht es darum, dass jeder Generator seinen eigenen isolierten Ausführungskontext hat. Zusammen mit der Tatsache, dass die Ausführung zwischen dem Generator-Iterator und dem Aufrufer unter Verwendung von
yield
bzw. umgeschaltet__next__()
wird, ähnelt dies Threads, bei denen das Betriebssystem den ausführenden Thread von Zeit zu Zeit zusammen mit dem Ausführungskontext (Stapel, Register, ...).Der Effekt davon ist auch vergleichbar: Sowohl der Generator-Iterator als auch der Aufrufer schreiten gleichzeitig in ihrem Ausführungsstatus voran, ihre Ausführungen sind verschachtelt. Wenn der Generator beispielsweise eine Berechnung durchführt und der Anrufer die Ergebnisse druckt, werden die Ergebnisse angezeigt, sobald sie verfügbar sind. Dies ist eine Form der Parallelität.
Diese Analogie ist jedoch nicht spezifisch
yield from
- sie ist eher eine allgemeine Eigenschaft von Generatoren in Python.quelle
get_list_values_as_xxx
sind einfache Generatoren mit einer einzelnen Leitungfor x in input_param: yield int(x)
und die beiden anderen mitstr
undfloat
Wo immer Sie einen Generator innerhalb eines Generators aufrufen, benötigen Sie eine "Pumpe", um
yield
die Werte erneut zu ermitteln :for v in inner_generator: yield v
. Wie der PEP hervorhebt, gibt es subtile Komplexitäten, die die meisten Menschen ignorieren. Nicht-lokale Flusskontrolle wiethrow()
ist ein Beispiel im PEP. Die neue Syntaxyield from inner_generator
wird überall dort verwendet, wo Sie zuvor die explizitefor
Schleife geschrieben hätten . Es ist jedoch nicht nur syntaktischer Zucker: Es behandelt alle Eckfälle, die von derfor
Schleife ignoriert werden . "Zuckerhaltig" zu sein, ermutigt die Menschen, es zu verwenden und so das richtige Verhalten zu erreichen.Diese Nachricht im Diskussionsthread spricht über diese Komplexität:
Ich kann nicht mit einem Vergleich mit Mikrofäden sprechen , außer zu beobachten, dass Generatoren eine Art Paralellismus sind. Sie können den angehaltenen Generator als einen Thread betrachten, der Werte über
yield
einen Consumer-Thread sendet . Die tatsächliche Implementierung ist möglicherweise nicht so (und die tatsächliche Implementierung ist offensichtlich für die Python-Entwickler von großem Interesse), betrifft jedoch nicht die Benutzer.Die neue
yield from
Syntax fügt der Sprache keine zusätzlichen Funktionen in Bezug auf das Threading hinzu, sondern erleichtert lediglich die korrekte Verwendung vorhandener Funktionen. Genauer gesagt erleichtert es einem unerfahrenen Verbraucher eines komplexen inneren Generators, der von einem Experten geschrieben wurde , diesen Generator zu durchlaufen, ohne seine komplexen Merkmale zu beeinträchtigen.quelle
Ein kurzes Beispiel hilft Ihnen dabei, einen
yield from
Anwendungsfall zu verstehen : Wert von einem anderen Generator abrufenquelle
print(*flatten([1, [2], [3, [4]]]))
yield from
Grundsätzlich verkettet Iteratoren auf effiziente Weise:Wie Sie sehen können, wird eine reine Python-Schleife entfernt. Das ist so ziemlich alles, aber das Verketten von Iteratoren ist in Python ein weit verbreitetes Muster.
Threads sind im Grunde eine Funktion, mit der Sie an völlig zufälligen Punkten aus Funktionen herausspringen und in den Zustand einer anderen Funktion zurückkehren können. Der Thread-Supervisor führt dies sehr oft aus, sodass das Programm alle diese Funktionen gleichzeitig auszuführen scheint. Das Problem ist, dass die Punkte zufällig sind. Sie müssen also die Sperrung verwenden, um zu verhindern, dass der Supervisor die Funktion an einem problematischen Punkt stoppt.
Generatoren sind Threads in diesem Sinne ziemlich ähnlich: Mit ihnen können Sie bestimmte Punkte angeben (wann immer sie vorhanden sind
yield
), an denen Sie ein- und aussteigen können. Auf diese Weise werden Generatoren als Coroutinen bezeichnet.Weitere Informationen finden Sie in diesen hervorragenden Tutorials zu Coroutinen in Python
quelle
throw()/send()/close()
es sich umyield
Funktionen handelt, dieyield from
offensichtlich ordnungsgemäß implementiert werden müssen, da sie den Code vereinfachen sollen. Solche Kleinigkeiten haben nichts mit Nutzung zu tun.chain
Funktion schreiben, da dieseitertools.chain
bereits vorhanden ist. Verwenden Sieyield from itertools.chain(*iters)
.In der angewandten Nutzung für den Asynchronous IO Koroutine ,
yield from
hat ein ähnliches Verhalten wieawait
in einer Koroutine Funktion . Beides wird verwendet, um die Ausführung von Coroutine auszusetzen.yield from
wird von der generatorbasierten Coroutine verwendet .await
wird fürasync def
Coroutine verwendet. (seit Python 3.5+)Wenn für Asyncio keine ältere Python-Version (dh> 3.5) unterstützt werden muss, ist
async def
/await
die empfohlene Syntax zum Definieren einer Coroutine. Somityield from
wird in einer Coroutine nicht mehr benötigt.Aber im Allgemeinen außerhalb der asyncio,
yield from <sub-generator>
hat noch eine andere Verwendung in Iterieren den Teilgenerators wie in der früheren Antwort erwähnt.quelle
Dieser Code definiert eine Funktion,
fixed_sum_digits
die einen Generator zurückgibt, der alle sechs Ziffern auflistet, so dass die Summe der Ziffern 20 beträgt.Versuchen Sie es ohne zu schreiben
yield from
. Wenn Sie einen effektiven Weg finden, lassen Sie es mich wissen.Ich denke, dass in Fällen wie diesem: der Besuch von Bäumen
yield from
den Code einfacher und sauberer macht.quelle
Einfach ausgedrückt,
yield from
bietet Schwanzrekursion für Iteratorfunktionen.quelle