web-dev-qa-db-ja.com

python 3.3.2+のyieldfromとyieldの違いは何ですか?

後python 3.3.2 + pythonは、ジェネレーター関数を作成するための新しい構文をサポートします

yield from <expression>

私はこれを簡単に試しました

>>> def g():
...     yield from [1,2,3,4]
...
>>> for i in g():
...     print(i)
...
1
2
3
4
>>>

使い方は簡単に見えますが、 [〜#〜] pep [〜#〜] ドキュメントは複雑です。私の質問は、前の利回りステートメントと比較して他に何か違いはありますか?ありがとう。

13
Erxin

ほとんどのアプリケーションでは、yield fromは、左からすべてを順番に繰り返し可能にします。

def iterable1():
    yield 1
    yield 2

def iterable2():
    yield from iterable1()
    yield 3

assert list(iterable2) == [1, 2, 3]

この投稿を見たユーザーの90%にとって、これは彼らにとって十分な説明になると思います。 yield from単純にデリゲートを右側のイテラブルに渡します。


コルーチン

ただし、ここでも重要な、より難解なジェネレーターの状況がいくつかあります。ジェネレーターについてあまり知られていない事実は、それらがコルーチンとして使用できるということです。これはあまり一般的ではありませんが、必要に応じてジェネレーターにデータを送信できます。

def coroutine():
    x = yield None
    yield 'You sent: %s' % x

c = coroutine()
next(c)
print(c.send('Hello world'))

余談ですが、これのユースケースは何か疑問に思われるかもしれません(そしてあなたは一人ではありません)。一例はcontextlib.contextmanagerデコレータです。コルーチンを使用して、特定のタスクを並列化することもできます。これが利用される場所はあまり多くありませんが、googleapp-engineのndbデータストアAPIは、かなり気の利いた方法で非同期操作に使用します。

ここで、別のジェネレーターからデータを生成しているジェネレーターにsendデータを送信するとします...元のジェネレーターはどのように通知されますか?答えは、ジェネレーターを自分でラップする必要があるpython2.xにはないということです。

def python2_generator_wapper():
    for item in some_wrapped_generator():
        yield item

少なくとも、多くの苦痛がないわけではありません。

def python2_coroutine_wrapper():
    """This doesn't work.  Somebody smarter than me needs to fix it. . .

    Pain.  Misery. Death lurks here :-("""
    # See https://www.python.org/dev/peps/pep-0380/#formal-semantics for actual working implementation :-)
    g = some_wrapped_generator()
    for item in g:
        try:
            val = yield item
        except Exception as forward_exception:  # What exceptions should I not catch again?
            g.throw(forward_exception)
        else:
            if val is not None:
                g.send(val)  # Oops, we just consumed another cycle of g ... How do we handle that properly ...

これはすべてyield fromで簡単になります。

def coroutine_wrapper():
    yield from coroutine()

yield fromは本当に(すべて!)を基礎となるジェネレーターに委任するからです。


セマンティクスを返す

問題のPEPもリターンセマンティクスを変更することに注意してください。 OPの質問に直接ではありませんが、あなたがそれを望んでいるなら、それはすぐに余談する価値があります。 python2.xでは、次のことはできません。

def iterable():
    yield 'foo'
    return 'done'

SyntaxErrorです。 yieldの更新により、上記の関数は無効になります。繰り返しますが、主なユースケースはコルーチンです(上記を参照)。ジェネレーターにデータを送信すると、プログラムの残りの部分が他のことを実行している間、魔法のように(おそらくスレッドを使用して)動作することができます。フロー制御がジェネレーターに戻ると、StopIterationが発生しますが(ジェネレーターの終わりでは通常どおり)、StopIterationにはデータペイロードがあります。これは、プログラマーが代わりに次のように書いた場合と同じです。

 raise StopIteration('done')

これで、呼び出し元はその例外をキャッチし、データペイロードを使用して何かを実行して、残りの人類に利益をもたらすことができます。

21
mgilson

一見すると、yield fromは次のアルゴリズムのショートカットです。

def generator1():
    for item in generator2():
        yield item
    # do more things in this generator

これは、ほとんどの場合、次のようになります。

def generator1():
    yield from generator2()
    # more things on this generator

英語:イテラブル内で使用される場合、yield fromは、最初のジェネレーターを呼び出すコードの観点から、そのアイテムが最初のジェネレーターからのものであるかのように、別のイテラブル内の各要素を発行します。

その作成の主な理由は、イテレータに大きく依存するコードの簡単なリファクタリングを可能にすることです-通常の関数を使用するコードは、非常に少ない追加コストで、タスクを分割する他の関数にリファクタリングされる1つの関数のブロックを持つことができます、コードの読み取りと保守を簡素化し、小さなコードスニペットの再利用性を高めます-

したがって、次のような大きな関数:

def func1():
    # some calculation
    for i in somesequence:
        # complex calculation using i 
        # ...
        # ...
        # ...
    # some more code to wrap up results
    # finalizing
    # ...

欠点なしに、このようなコードになることができます:

def func2(i):
    # complex calculation using i 
    # ...
    # ...
    # ...
    return calculated_value

def func1():
    # some calculation
    for i in somesequence:
         func2(i)
    # some more code to wrap up results
    # finalizing
    # ...

ただし、イテレータに到達するときのフォーム

def generator1():
    for item in generator2():
        yield item
    # do more things in this generator

for item in generator1():
    # do things

generator2から消費されるアイテムごとに、実行中のコンテキストを最初にgenerator1に切り替え、そのコンテキストでは何も行わず、cotnextをgenerator2に切り替える必要があります。 1つは値を生成し、generator1への別の中間コンテキストスイッチがあり、それらの値を消費する実際のコードに値を取得します。

これらの中間コンテキストスイッチからの歩留まりが回避されるため、チェーンされたイテレーターが多数ある場合、かなりのリソースを節約できます。コンテキストは、最も外側のジェネレーターを消費するコンテキストから最も内側のジェネレーターに直接切り替わり、中間ジェネレーターのコンテキストを完全にスキップします。内側のものが使い果たされるまで。

その後、言語は中間コンテキストを通じてこの「調整」を利用して、これらのジェネレーターをコルーチンとして使用しました。非同期呼び出しを行うことができる関数です。 https://www.python.org/dev/peps/pep-3156/ で説明されているように、適切なフレームワークが適切に配置されている場合、これらのコルーチンは、いつ呼び出すかという方法で記述されます。解決に長い時間がかかる関数(ネットワーク操作、または別のスレッドにオフロードされる可能性のあるCPUを集中的に使用する操作のため)-その呼び出しはyield fromステートメントで行われます-フレームワークのメインループは次に調整します呼び出された高価な関数が適切にスケジュールされ、実行を再開するようにします(フレームワークのメインループは常にコルーチン自体を呼び出すコードです)。高価な結果の準備ができると、フレームワークは呼び出されたコルーチンを使い果たされたジェネレーターのように動作させ、最初のコルーチンの実行を再開します。

プログラマーの観点からは、コードが中断することなくまっすぐに実行されているかのようです。プロセスの観点からは、コルーチンは高額な呼び出しの時点で一時停止され、他の(おそらく同じコルーチンへの並列呼び出し)が実行され続けました。

したがって、Webクローラーの一部として次のようなコードを書くことができます。

@asyncio.coroutine
def crawler(url):
   page_content = yield from async_http_fetch(url)
   urls = parse(page_content)
   ...

Asyncioループから呼び出されると、数十のhtmlページを同時にフェッチできます。

Python 3.4は、この種の機能のデフォルトプロバイダーとしてasyncioモジュールをstdlibに追加しました。それは非常にうまく機能したので、Python 3.5では、コルーチンと非同期呼び出しを上記のジェネレーターの使用法と区別するために、いくつかの新しいキーワードが言語に追加されました。これらは https://www.python.org/dev/peps/pep-0492/

10
jsbueno

これを説明する例を次に示します。

>>> def g():
...     yield from range(5)
... 
>>> list(g())
[0, 1, 2, 3, 4]
>>> def g():
...     yield range(5)
... 
>>> list(g())
[range(0, 5)]
>>>

yield fromはイテラブルの各アイテムを生成しますが、yieldはイテラブル自体を生成します。

2
zondo