最初に、邪魔にならないものを1つ取得しましょう。にyield from g
相当する説明は、すべてが何であるかについてfor v in g: yield v
正当化しさえしませんyield from
。正直なところ、ループをyield from
拡張するだけの場合は、言語にfor
追加yield from
することを保証するものではなく、Python 2.xに実装されている新機能の全体を排除するものではありません。
何yield from
行うことは、それが発信者とサブジェネレータの間に透明の双方向接続を確立します:
(TCPについて話している場合、yield from g
「クライアントのソケットを一時的に切断して、この他のサーバーソケットに再接続する」という意味になるかもしれません。)
ところで、ジェネレータにデータを送信する意味さえわからない場合は、まずすべてを削除し、コルーチンについて読む必要があります。コルーチンは非常に便利ですが(サブルーチンと比較してください)、残念ながらPythonではあまり知られていません。デイブビーズリーのコルーチンに関する好奇心旺盛なコースは素晴らしいスタートです。クイックプライマーについては、スライド24〜33を参照してください。
収量を使用してジェネレーターからデータを読み取る
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
手動で反復する代わりにreader()
、それを行うことができyield from
ます。
def reader_wrapper(g):
yield from g
これでうまくいき、コードを1行削除しました。そしておそらくその意図は少し明確です(またはそうではありません)。しかし、人生は変わりません。
収量を使用してジェネレーター(コルーチン)にデータを送信する-パート1
次に、もっと面白いことをしましょう。writer
送信されたデータを受け入れ、ソケットやfdなどに書き込むという、コルーチンを作成してみましょう。
def writer():
"""A coroutine that writes data *sent* to it to fd, socket, etc."""
while True:
w = (yield)
print('>> ', w)
ここで問題は、ラッパー関数がライターへのデータ送信をどのように処理すれば、ラッパーに送信されるすべてのデータが透過的に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
ラッパーは(当然のことながら)送信されたデータを受け入れる必要がありStopIteration
、forループが使い果たされたときにも処理する必要があります。明らかに、ただやるだけでfor x in coro: yield x
はダメです。これが機能するバージョンです。
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
または、これを行うことができます。
def writer_wrapper(coro):
yield from coro
これにより、6行のコードが節約され、はるかに読みやすくなり、機能します。マジック!
データをジェネレーターに送信する-パート2-例外処理
もっと複雑にしましょう。ライターが例外を処理する必要がある場合はどうなりますか?writer
ハンドルaがあるSpamException
としましょう。ハンドルa ***
に遭遇した場合に出力します。
class SpamException(Exception):
pass
def writer():
while True:
try:
w = (yield)
except SpamException:
print('***')
else:
print('>> ', w)
変わらないとどうなるwriter_wrapper
?うまくいきますか?やってみよう
# 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
ええと、x = (yield)
例外が発生し、すべてがクラッシュして停止するため、機能しません。それを機能させましょうが、手動で例外を処理し、それらをサブジェネレータに送信またはスローします(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
これは機能します。
# Result
>> 0
>> 1
>> 2
***
>> 4
しかし、これもそうです!
def writer_wrapper(coro):
yield from coro
yield from
透過ハンドルは、値を送信またはサブジェネレータに値を投げます。
ただし、これはまだすべての主要なケースをカバーしているわけではありません。外部ジェネレーターが閉じている場合はどうなりますか?サブジェネレーターが値を返す場合はどうですか(そうです、Python 3.3以降では、ジェネレーターは値を返すことができます)。戻り値はどのように伝播する必要がありますか?それyield from
はすべての隅のケースを透過的に処理することは本当に印象的です。yield from
魔法のように機能し、それらすべてのケースを処理します。
個人的にyield from
は、双方向の性質が明確にならないため、キーワードの選択としては不適切だと感じています。他にも提案されたキーワードがありました(delegate
新しいキーワードを言語に追加することは、既存のキーワードを組み合わせるよりもはるかに難しいため、拒否されました)。
要約すると、それは考えるのがベストだyield from
としてtransparent two way channel
、発信者とサブジェネレータの間。
参照:
- PEP 380-サブジェネレーターに委任するための構文(Ewing)[v3.3、2009-02-13]
- PEP 342-拡張ジェネレータを介したコルーチン(GvR、Eby)[v2.5、2005-05-10]