1. ホーム
  2. パイソン

[解決済み】Python 3.3の "yield from "構文の主な用途は何ですか?

2022-03-17 18:09:16

質問

を理解するのに苦労しています。 PEP 380 .

  1. という場面は? yield from は有効ですか?
  2. 古典的な使用例とは?
  3. なぜマイクロスレッドと比較されるのですか?

これまで、私はジェネレータは使っていましたが、コルーチン( PEP-342 ). いくつかの類似点はありますが、ジェネレータとコルーチンは基本的に2つの異なる概念です。コルーチン(ジェネレータだけでなく)を理解することが、新しい構文を理解するための鍵となります。

IMHO コルーチンはPythonの機能の中で最も曖昧なものです。 ほとんどの本では、役に立たないし、面白くもないように書かれています。


素晴らしい回答をありがとうございました。 アグフ とリンクしているコメント デービッド・ビーズリー・プレゼンテーション .

解決するには?

まず、ひとつだけ整理しておきましょう。という説明は yield from g と同等です。 for v in g: yield v というのは、正当に評価することすらできません。 何に対して yield from がすべてです。なぜなら、正直に言うと、もしすべての yield from を拡張するだけです。 for ループを追加する必要はありません。 yield from を言語に追加し、Python 2.xで実装される新機能の束を排除してしまうのです。

yield from は、それは 呼び出し側と副生成側の間の透過的な双方向接続を確立する。 :

  • 接続は、生成される要素だけでなく、すべても正しく伝搬するという意味で、"transparent" です(例えば、例外は伝搬されます)。

  • 接続はquot;bidirectional(双方向)である。 から ジェネレータです。

( 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を使ったジェネレータ(コルーチン)へのデータ送信 - Part 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 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. PEP 380 - サブジェネレータに委任するための構文(Ewing)[v3.3, 2009-02-13]
  2. PEP 342 - 拡張ジェネレータによるコルーチン (GvR, Eby) [v2.5, 2005-05-10].