実際には、Python 3.3 の「yield from」構文の主な用途は何ですか? 質問する

実際には、Python 3.3 の「yield from」構文の主な用途は何ですか? 質問する

理解するのが難しいペップ380

  1. どのような状況でyield from役立つのでしょうか?
  2. 典型的な使用例は何ですか?
  3. なぜマイクロスレッドと比較されるのでしょうか?

これまで私はジェネレーターを使ってきましたが、コルーチン(ペップ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

参考文献:

  1. ペップ380- サブジェネレータへの委任構文 (Ewing) [v3.3、2009-02-13]
  2. ペップ342- 拡張ジェネレータによるコルーチン (GvR、Eby) [v2.5、2005-05-10]

おすすめ記事