python-マルチディスク-コヒーレントyield from yield from/asyncio非同期io/コヒーレントの最も簡単な例を徹底的に詳しく説明する

8442 ワード

Python3.3バージョンのPEP 380には、yield fromシンタックスが追加され、1つのgeneratorジェネレータがその一部の操作を別のジェネレータに委任することができる.その主な動力は,生成器をsendthrowの方法を有する複数のサブ生成器に容易に分けることができ,1つの大きな関数が複数のサブ関数に分けることができるように簡単であることである.Pythonのジェネレータは、コモンcoroutineの形式であるが、その限界は、直接呼び出し者yield値のみに適用できることである.これはyieldを含むコードが他のコードのように分離されて個別の関数に置かれないことを意味する.これもyield fromが解決しなければならないことだ.【yield fromは主にサブジェネレータに操作タスクを委任するために設計されている】が、yield fromは任意の反復器に操作を委任することができる.簡単な反復器の場合、yield from iterableは本質的にfor item in iterable:yield itemの略語版に等しく、以下に示す.
>>> def g(x):
...     yield from range(x, 0, -1)
...     yield from range(x)
...
>>> list(g(5))
[5, 4, 3, 2, 1, 0, 1, 2, 3, 4]

しかしながら、通常のサイクルとは異なり、yield fromは、サブジェネレータが呼び出し元から送信された情報を直接受信したり、呼び出し時に発生した異常を投げ出したりして、以下に示すように、委任プロダクションに値を返すことを可能にする.
>>> def accumulate():    #     ,     None   ,      None,       
...     tally = 0        #   for             ,tally     
...     while 1:
...         next = yield
...         if next is None:
...             return tally
...         tally += next
...
>>> def gather_tallies(tallies):    #      ,              
...     while 1:
...         tally = yield from accumulate()   #   for       accumulate()  from, item  yield,  ⚠️      
...         tallies.append(tally)   #              append  ,       [6,10]
...
>>> tallies = []
>>> acc = gather_tallies(tallies)
>>> next(acc)    #               
>>> for i in range(4):   #   for      acc,   for          ,    ,for        ,        yield from                   
...     acc.send(i)
...
>>> acc.send(None)    #        
>>> for i in range(5):
...     acc.send(i)
...
>>> acc.send(None)    #        
>>> tallies    #       
[6, 10]  

以下にまとめる.
  • 反復器(サブジェネレータを指すことができる)によって生成された値は、呼び出し者
  • に直接返す.
  • send()の方法を使用して委任された生産装置(すなわち、外部生産装置)に送られた値は、反復装置に直接渡される.send値がNoneの場合、反復器next()メソッドが呼び出される.Noneでない場合は、反復器のsend()メソッドが呼び出されます.反復器の呼び出しに対してStopIteration異常が発生した場合、委任生産器はyield fromの後の文を実行し続けるように回復します.反復器に他の異常が発生した場合は、委任プロダクションに渡されます.
  • GeneratorExit異常を除いた他の委任された生産機に投げ込まれた異常は、反復器のthrow()メソッドに渡される.反復器throw()呼び出しによってStopIteration例外が発生した場合、代理プロダクションは回復して実行を続行し、他の例外は代理プロダクションに渡されます.
  • GeneratorExit異常がデリバリプロダクションに投げ出された場合、またはデリバリプロダクションのclose()メソッドが呼び出され、反復器にclose()があれば呼び出されます.close()呼び出しに異常が発生した場合、例外は委任プロダクションに渡されます.そうでない場合、代理プロダクションはGeneratorExit例外を放出します.
  • 反復器が終了し、例外が放出されると、yield from式の値は、そのStopIteration異常の最初のパラメータである.
  • ジェネレータのreturn expr文は、ジェネレータから終了し、StopIteration(expr)例外を放出します.

  • stackoverflow上の古典的な解釈
    yield fromは、呼び出し者とサブジェネレータとの間の透明な双方向チャネルを提供するものと見なされる.サブジェネレータからデータを取得し、サブジェネレータにデータを転送することを含む.
    1.yield fromでジェネレータからデータを読み込む
    def reader():
        #              
        for i in range(4):
            yield '<< %s' % i
    
    def reader_wrapper(g):
        #      reader      
        for v in g:
            yield v
    
    wrap = reader_wrapper(reader())
    for i in wrap:
        print(i)
    
    #   :
    << 0
    << 1
    << 2
    << 3
    
    reader_wrapper(g)関数のループをyield from文で置き換えることができます.次のようにします.
    def reader_wrapper(g):
        yield from g
    

    2.yield from文によるジェネレータへのデータ転送
    まずジェネレータwriterを作成し、転送されたデータを受信し、ソケット、ファイルなどに書き込む.
    def writer():
        #   send     ,           
        while True:
            w =  yield    # w  send     
            print('>> ', w)
    

    現在の問題は、パッケージ関数がwriter関数にどのようにデータを転送し、パッケージに渡されたデータがwriter関数に表示されて伝達されるかということです.すなわち、以下の結果が期待される.
    def writer_wrapper(coro):
        # TBD
        pass
    
    w = writer()
    wrap = writer_wrapper(w)
    wrap.send(None)  #           
    for i in range(4):
        wrap.send(i)
    
    #     :
    >>  0
    >>  1
    >>  2
    >>  3
    
    yield from文を使用して、reader_wrapper(g)関数のループを次のように置き換えることができます.
    def reader_wrapper(g):
        yield from g
    

    効果は同じで、コードはもっと簡潔ですか.
    2.yield from文によるジェネレータへのデータ転送
    まずジェネレータwriterを作成し、転送されたデータを受信し、ソケット、ファイルなどに書き込む.
    def writer():
        #   send     ,           
        while True:
            w = (yield)    # w  send     
            print('>> ', w)
    

    現在の問題は、パッケージ関数がwriter関数にどのようにデータを転送し、パッケージに渡されたデータがwriter関数に表示されて伝達されるかということです.
    すなわち、以下の結果が期待される.
    def writer_wrapper(coro):
        # TBD
        pass
    
    w = writer()
    wrap = writer_wrapper(w)
    wrap.send(None)  #           
    for i in range(4):
        wrap.send(i)
    
    #     :
    >>  0
    >>  1
    >>  2
    >>  3
    

    パッケージ領域はデータを受信し、ジェネレータに渡す必要があることは明らかであり、forサイクル消費を処理する必要があるのはジェネレータによって発生したStopIteration異常であり、パッケージがforサイクルだけでは需要を満たすことができないことは明らかであり、状況を満たす一般的なバージョンは以下の通りである.
    def writer_wrapper(coro1):
        coro1.send(None)  #           
        while True:
            try:
                x = (yield)  # x  send     
                coro1.send(x)  #    x send writer    
            except StopIteration:    #            
                pass
    

    包装器もジェネレータで、上のすべての複雑な書き方もyield fromで置き換えることができます.
    def writer_wrapper(coro2):
        yield from coro2
    

    一気に多くのコードが少なくなったのは、奇跡を目撃したのではないでしょうか.
    3.yield fromによるジェネレータへのデータ転送–異常処理
    さらに、サブジェネレータであるwriterが異常を処理する必要がある場合はどうすればいいですか?WriterがSpamException異常を処理する必要があると仮定し、この異常印刷***に遭遇した場合、コードは以下の通りである.
    class SpamException(Exception):
        pass
    
    def writer():
        while True:
            try:
                w = (yield)
            except SpamException:
                print('***')
            else:
                print('>> ', w)
    

    前の一般バージョンのパッケージwriter_wrapper(coro1)を使用すると、どのような結果が得られますか?試験は以下の通りである.
    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)
    
    #     :
    >>  0
    >>  1
    >>  2
    ***
    >>  4
    
    #     :
    >>  0
    >>  1
    >>  2
    Traceback (most recent call last):
      File ... in 
        wrap.throw(SpamException)
      File ... in writer_wrapper
        x = (yield)
    __main__.SpamException
    
    x = (yield)文は異常を起こし、実行を停止するだけであるため、これは通用しません.梱包器writer_wrapper(coro1)に手動で異常処理を追加し、サブジェネレータwriterに異常を渡したり投げたりすることができます.コードは以下の通りです.
    def writer_wrapper(coro1):
        #              
        coro1.send(None)    #           
        while True:
            try:
                try:
                    x = (yield)
                except Exception as e:   #     
                    coro1.throw(e)
                else:
                    coro1.send(x)
            except StopIteration:
                pass
    

    同様に、この複雑なコードの山は、yield from文で置き換えることができ、機能は完全に同じです!!!
    def writer_wrapper(coro):
        yield from coro
    

    重要なコードを3回貼ります!3回!3回!
    ここで,yield fromがサブジェネレータへの処理伝値および異常サブジェネレータの放出を示す意味が理解できるだろう.もちろんyield fromにはこの2つの処理状況だけでなく、外部ジェネレータが閉じ、サブジェネレータも閉じます.サブジェネレータは、上記の2番目のコード例などの価値のある状況を返します.
    要するに、これは魔法の文で、それも協程の重要な構成部分で、協程については、まだ勉強を続ける必要があります.
    译文:yield from主に何の役に立つ?
    協程の最も簡単で分かりやすい小例(補)
    Pythonのコラボレーションとジェネレータは似ていますが、少し違います.主な違いは次のとおりです.
    ジェネレータがデータの生産者協程であり、データの消費者Pythonが実現したgrepは良い例である.
        def grep(pattern):
            print("Searching for", pattern)
            while True:
                line = (yield)       #     ,    pattern print(line)
                if pattern in line:
                    print(line)
    

    待って!yieldは何を返しましたか?ああ、私たちはもうそれを協力に変えました.初期値は含まれません.逆に、外部から値を渡します.send()法により値を伝達することができる.次の例があります.
        search = grep('coroutine')
        next(search)
        #output: Searching for coroutine
        search.send("I love you")
        search.send("Don't you love me?")
        search.send("I love coroutine instead!")
        #output: I love coroutine instead!
    

    送信された値はyieldによって受信されます.なぜnext()メソッドを実行するのですか?このようにするのは、協力を開始するためです.コプロセッサに含まれるジェネレータは、直ちに実行されるのではなく、next()メソッドによってsend()メソッドに応答する.したがって、next()メソッドを使用してyield式を実行する必要があります.close()メソッドを呼び出すことによって、コヒーレントを閉じることができます.このように:
        search = grep('coroutine')
        search.close()