理解するのが難しいペップ380。
- どのような状況で
yield from
役立つのでしょうか? - 典型的な使用例は何ですか?
- なぜマイクロスレッドと比較されるのでしょうか?
これまで私はジェネレーターを使ってきましたが、コルーチン(ペップ342)。いくつかの類似点はあるものの、ジェネレータとコルーチンは基本的に 2 つの異なる概念です。コルーチン (ジェネレータだけでなく) を理解することが、新しい構文を理解する鍵となります。
私の意見では、コルーチンは Python で最もわかりにくい機能であり、ほとんどの本では役に立たず、面白くないものとして扱われています。
素晴らしい回答をありがとうございました。特にエーエフそして彼のコメントはデビッド・ビーズリーのプレゼンテーション。
ベストアンサー1
まず、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 ではあまり知られていません。Dave Beazley のコルーチンに関する興味深いコース素晴らしいスタートです。スライド24~33を読む簡単な入門書です。
yield fromを使用してジェネレータからデータを読み取る
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 行削減できました。おそらく意図は少し明確になったと思います (あるいはそうでないかもしれません)。しかし、人生を変えるほどのものではありません。
yield from を使用してジェネレータ (コルーチン) にデータを送信する - パート 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
、例外が発生した場合にSpamException
出力するとします。***
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
参考文献: