Lassen Sie uns zunächst eine Sache aus dem Weg räumen. Die Erklärung, die yield from g
ist gleichbedeutend mit for v in g: yield v
wird dem nicht einmal ansatzweise gerecht zu was yield from
dreht sich alles um. Denn, seien wir ehrlich, wenn alle yield from
ist die Erweiterung der for
Schleife, dann ist es nicht gerechtfertigt, die yield from
der Sprache und verhindern, dass eine ganze Reihe neuer Funktionen in Python 2.x implementiert werden.
Was yield from
ist es stellt eine transparente bidirektionale Verbindung zwischen dem Anrufer und dem Untergenerator her :
-
Die Verbindung ist "transparent" in dem Sinne, dass sie alles korrekt weiterleitet, nicht nur die erzeugten Elemente (z. B. werden Ausnahmen weitergegeben).
-
Die Verbindung ist "bidirektional" in dem Sinne, dass Daten sowohl gesendet als auch empfangen werden können. de y a einen Generator.
( Wenn wir über TCP sprechen würden, yield from g
könnte bedeuten: "Trenne jetzt vorübergehend den Socket meines Clients und verbinde ihn wieder mit diesem anderen Server-Socket". )
Übrigens, wenn Sie nicht sicher sind, was Senden von Daten an einen Generator auch bedeutet, dass Sie alles stehen und liegen lassen müssen und über Koroutinen erstens - sie sind sehr nützlich (im Gegensatz zu Unterroutinen ), aber leider weniger bekannt in Python. Dave Beazleys kurioser Kurs über Coroutines ist ein ausgezeichneter Anfang. Folien 24-33 lesen für eine kurze Einweisung.
Lesen von Daten aus einem Generator unter Verwendung der Ausbeute von
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 eine Iteration über reader()
können wir einfach yield from
es.
def reader_wrapper(g):
yield from g
Das funktioniert, und wir haben eine Zeile Code eingespart. Und wahrscheinlich ist die Absicht ein wenig klarer (oder auch nicht). Aber nichts Lebensveränderndes.
Senden von Daten an einen Generator (Coroutine) mit yield from - Teil 1
Jetzt wollen wir etwas Interessanteres machen. Lassen Sie uns eine Coroutine namens writer
die an sie gesendete Daten annimmt und in ein Socket, fd usw. schreibt.
def writer():
"""A coroutine that writes data *sent* to it to fd, socket, etc."""
while True:
w = (yield)
print('>> ', w)
Nun stellt sich die Frage, wie die Wrapper-Funktion das Senden von Daten an den Writer handhaben soll, damit alle Daten, die an den Wrapper gesendet werden, transparent die an den 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 akzeptieren die Daten, die an ihn gesendet werden (natürlich) und sollte auch die StopIteration
wenn die for-Schleife erschöpft ist. Offensichtlich macht man einfach for x in coro: yield x
nicht ausreicht. 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 dies tun.
def writer_wrapper(coro):
yield from coro
Das spart 6 Zeilen Code, macht ihn viel lesbarer und funktioniert einfach. Magie!
Senden von Daten an einen Generatorertrag von - Teil 2 - Ausnahmebehandlung
Machen wir es noch komplizierter. Was ist, wenn unser Autor Ausnahmen behandeln muss? Sagen wir, die writer
behandelt ein SpamException
und er druckt ***
wenn es auf eine solche trifft.
class SpamException(Exception):
pass
def writer():
while True:
try:
w = (yield)
except SpamException:
print('***')
else:
print('>> ', w)
Was, wenn wir uns nicht ändern? writer_wrapper
? Funktioniert es? Versuchen wir es
# 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)
löst einfach die Ausnahme aus und alles kommt zum Stillstand. Lassen Sie es funktionieren, aber behandeln Sie Ausnahmen manuell und senden Sie sie oder werfen Sie sie in den Untergenerator ( 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 hier tut es auch!
def writer_wrapper(coro):
yield from coro
En yield from
übernimmt transparent das Senden der Werte oder das Übergeben von Werten an den Untergenerator.
Damit sind aber immer noch nicht alle Eckfälle abgedeckt. Was passiert, wenn der äußere Generator geschlossen ist? Was ist mit dem Fall, dass 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
die transparente Behandlung aller Eckfälle ist wirklich beeindruckend . yield from
wie von Zauberhand funktioniert und alle diese Fälle behandelt.
Ich persönlich finde yield from
ist ein schlecht gewähltes Schlüsselwort, denn es macht die Zwei-Wege Natur offensichtlich. Es wurden weitere Schlüsselwörter vorgeschlagen (wie delegate
wurden jedoch abgelehnt, weil das Hinzufügen eines neuen Schlüsselworts zur Sprache viel schwieriger ist als die Kombination bestehender Schlüsselwörter.
Zusammenfassend lässt sich sagen, dass es am besten ist, an Folgendes zu denken yield from
als transparent two way channel
zwischen dem Aufrufer und dem Untergenerator.
Referenzen:
- PEP 380 - Syntax für die Delegierung an einen Untergenerator (Ewing) [v3.3, 2009-02-13]
- PEP 342 - Coroutinen über erweiterte Generatoren (GvR, Eby) [v2.5, 2005-05-10]