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 , worum yield from
es geht, nicht einmal gerecht . Seien wir ehrlich, wenn nur yield from
die for
Schleife erweitert wird, ist es nicht gerechtfertigt yield 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
def reader():
"""A generator that fakes a read from a file, socket, etc."""
for i in range(4):
yield '<< %s' % i
def reader_wrapper(g):
# Manually iterate over data produced by reader
for v in g:
yield v
wrap = reader_wrapper(reader())
for i in wrap:
print(i)
# Result
<< 0
<< 1
<< 2
<< 3
Anstatt manuell zu iterieren reader()
, können wir es einfach tun yield from
.
def reader_wrapper(g):
yield from g
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.
def writer():
"""A coroutine that writes data *sent* to it to fd, socket, etc."""
while True:
w = (yield)
print('>> ', w)
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()
.
def writer_wrapper(coro):
# TBD
pass
w = writer()
wrap = writer_wrapper(w)
wrap.send(None) # "prime" the coroutine
for i in range(4):
wrap.send(i)
# Expected result
>> 0
>> 1
>> 2
>> 3
Der Wrapper muss die an ihn gesendeten Daten akzeptieren (offensichtlich) und sollte auch das verarbeiten, StopIteration
wenn die for-Schleife erschöpft ist. Offensichtlich reicht for x in coro: yield x
es nicht, nur etwas zu tun. Hier ist eine Version, die funktioniert.
def writer_wrapper(coro):
coro.send(None) # prime the coro
while True:
try:
x = (yield) # Capture the value that's sent
coro.send(x) # and pass it to the writer
except StopIteration:
pass
Oder wir könnten das tun.
def writer_wrapper(coro):
yield from coro
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 werden SpamException
gedruckt, ***
wenn sie auf einen treffen.
class SpamException(Exception):
pass
def writer():
while True:
try:
w = (yield)
except SpamException:
print('***')
else:
print('>> ', w)
Was ist, wenn wir uns nicht ändern writer_wrapper
? Funktioniert es? Lass es uns versuchen
# writer_wrapper same as above
w = writer()
wrap = writer_wrapper(w)
wrap.send(None) # "prime" the coroutine
for i in [0, 1, 2, 'spam', 4]:
if i == 'spam':
wrap.throw(SpamException)
else:
wrap.send(i)
# Expected Result
>> 0
>> 1
>> 2
***
>> 4
# Actual Result
>> 0
>> 1
>> 2
Traceback (most recent call last):
... redacted ...
File ... in writer_wrapper
x = (yield)
__main__.SpamException
Ä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
)
def writer_wrapper(coro):
"""Works. Manually catches exceptions and throws them"""
coro.send(None) # prime the coro
while True:
try:
try:
x = (yield)
except Exception as e: # This catches the SpamException
coro.throw(e)
else:
coro.send(x)
except StopIteration:
pass
Das funktioniert.
# Result
>> 0
>> 1
>> 2
***
>> 4
Aber das tut es auch!
def writer_wrapper(coro):
yield from coro
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 (wie delegate
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
als transparent two way channel
zwischen dem Anrufer und dem Teilgenerator.
Verweise:
- PEP 380 - Syntax zum Delegieren an einen Untergenerator (Ewing) [v3.3, 2009-02-13]
- PEP 342 - Coroutinen über erweiterte Generatoren (GvR, Eby) [v2.5, 2005-05-10]