Pythonでのジェネレータの詳細


前言:前のブログでは、ジェネレータ:Python反復器とジェネレータのまとめ【推奨コレクション】を紹介しましたが、このブログではPythonのジェネレータについてシステム紹介します.
文書ディレクトリ
  • 一、ジェネレータ概要
  • 二、使用()作成ジェネレータ
  • 三、yieldを使用してジェネレータ
  • を作成する
  • 四、sendを使用して
  • を起動する

    一、ジェネレータの概要
    反復器を用いて,反復してデータを取得するたびに(next()法により)特定の法則に従って生成することができる.しかし、反復器を実装する場合、現在反復されている状態については、現在の状態に基づいて次のデータを生成するために、自分で記録する必要があります.現在の状態を記録し、next()関数と組み合わせて反復的に使用するために、より簡便な構文、すなわちジェネレータを採用することができる.ジェネレータは特殊な反復器です.
    二、()を使用してジェネレータを作成する
    ジェネレータを作成するには、さまざまな方法があります.1つ目の方法は簡単で,1つのリスト生成式の[]を()に変更すると,以下のようになる.
    In [15]: L = [ x*2 for x in range(5)]
    
    In [16]: L
    Out[16]: [0, 2, 4, 6, 8]
    
    In [17]: G = ( x*2 for x in range(5))
    
    In [18]: G
    Out[18]: <generator object <genexpr> at 0x7f626c132db0>
    

    LとGを作成する違いは、最外層の[]と()のみであり、Lはリストであり、Gはジェネレータである.リストLの各要素を直接印刷することができ、ジェネレータGでは、next()関数、forループ、list()などの方法で反復器の使用方法で使用することができる.
    In [19]: next(G)
    Out[19]: 0
    
    In [20]: next(G)
    Out[20]: 2
    
    In [21]: next(G)
    Out[21]: 4
    
    In [22]: next(G)
    Out[22]: 6
    
    In [23]: next(G)
    Out[23]: 8
    
    In [24]: next(G)
    ---------------------------------------------------------------------------
    StopIteration                             Traceback (most recent call last)
    <ipython-input-24-380e167d6934> in <module>()
    ----> 1 next(G)
    
    StopIteration:
    
    In [25]: G = ( x*2 for x in range(5))
    
    In [26]: for x in G:
       ....:     print(x)
       ....:     
    0
    2
    4
    6
    8
    

    三、yieldを使ってジェネレータを作成する
    generatorはとても強いです.推定アルゴリズムが複雑であれば,類似リスト生成式のforサイクルでは実現できない場合,関数で実現することも可能である.
    前節で述べたフィボナッチ数列を例に挙げて、前節で反復器を用いた実装方法を思い出します.
    class FibIterator(object):
        """         """
        def __init__(self, n):
            """
            :param n: int,         n  
            """
            self.n = n
            # current                  
            self.current = 0
            # num1         ,            0
            self.num1 = 0
            # num2        ,            1
            self.num2 = 1
    
        def __next__(self):
            """ next()           """
            if self.current < self.n:
                num = self.num1
                self.num1, self.num2 = self.num2, self.num1+self.num2
                self.current += 1
                return num
            else:
                raise StopIteration
    
        def __iter__(self):
            """    __iter__      """
            return self
    

    注意:反復器で実装する方法では、反復の状態をいくつかの変数(n、current、num 1、num 2)で保存します.ジェネレータで実現しましょう.
    In [30]: def fib(n):
       ....:     current = 0
       ....:     num1, num2 = 0, 1
       ....:     while current < n:
       ....:         num = num1
       ....:         num1, num2 = num2, num1+num2
       ....:         current += 1
       ....:         yield num
       ....:     return 'done'
       ....:
    
    In [31]: F = fib(5)
    
    In [32]: next(F)
    Out[32]: 1
    
    In [33]: next(F)
    Out[33]: 1
    
    In [34]: next(F)
    Out[34]: 2
    
    In [35]: next(F)
    Out[35]: 3
    
    In [36]: next(F)
    Out[36]: 5
    
    In [37]: next(F)
    ---------------------------------------------------------------------------
    StopIteration                             Traceback (most recent call last)
    <ipython-input-37-8c2b02b4361a> in <module>()
    ----> 1 next(F)
    
    StopIteration: done
    

    ジェネレータ実装を使用する方法では、反復器__next__法で実装されていた基本論理を1つの関数に配置して実装したが、反復ごとに数値を返すreturnyieldに変換すると、新しく定義された関数は関数ではなくジェネレータになる.簡単に言えばdefにyieldのキーワードがある限りジェネレータと呼ぶ
    このとき、関数を呼び出すように(ケースではF=fib(5))ジェネレータを使用すると、実行関数体ではなく、ジェネレータオブジェクト(ケースではF)が返され、反復器を使用するようにジェネレータを使用できます.
    In [38]: for n in fib(5):
       ....:     print(n)
       ....:     
    1
    1
    2
    3
    5
    

    しかし,generatorをforサイクルで呼び出すと,generatorのreturn文の戻り値が得られないことが分かった.戻り値を取得するには、StopIterationエラーを取得する必要があります.戻り値はStopIterationのvalueに含まれます.
    In [39]: g = fib(5)
    
    In [40]: while True:
       ....:     try:
       ....:         x = next(g)
       ....:         print("value:%d"%x)      
       ....:     except StopIteration as e:
       ....:         print("      :%s"%e.value)
       ....:         break
       ....:     
    value:1
    value:1
    value:2
    value:3
    value:5
          :done
    

    まとめ:
  • yieldキーワードを使用した関数は、関数ではなくジェネレータ(yieldを使用した関数がジェネレータ)です.
  • yieldキーワードには2つの役割があります.
  • 現在の運転状態(ブレークポイント)を保存し、ジェネレータ(関数)が一時停止します.
  • は、yieldキーワードの後の式の値を戻り値として返し、この場合、returnの役割を果たしていると理解できる.

  • は、next()関数を使用して、ジェネレータをブレークポイントから実行し続けることができ、すなわち、ジェネレータ(関数)を起動させることができる.
  • Python 3のジェネレータは、returnを使用して最終的に実行された戻り値を返すことができますが、Python 2のジェネレータは、returnを使用して戻り値を返すことはできません(つまり、returnを使用してジェネレータから終了することができますが、return以降は式がありません).

  • 四、sendで起動するnext()関数を使用してジェネレータを起動して実行を継続できるほか、send()関数を使用して実行を起動することもできます.send()関数を使用する利点の1つは、起動と同時にブレークポイントに追加のデータを転送できることである.
    例:yieldに実行するとgen関数作用が一時保存され、iの値が返される.tempは次のc.send(「python」)を受信し,sendが送信した値,c.next()はc.send(None)に等価である.
    In [10]: def gen():
       ....:     i = 0
       ....:     while i<5:
       ....:         temp = yield i
       ....:         print(temp)
       ....:         i+=1
    

    sendの使用
    In [43]: f = gen()
    
    In [44]: next(f)
    Out[44]: 0
    
    In [45]: f.send('haha')
    haha
    Out[45]: 1
    
    In [46]: next(f)
    None
    Out[46]: 2
    
    In [47]: f.send('haha')
    haha
    Out[47]: 3
    

    next関数の使用
    In [11]: f = gen()
    
    In [12]: next(f)
    Out[12]: 0
    
    In [13]: next(f)
    None
    Out[13]: 1
    
    In [14]: next(f)
    None
    Out[14]: 2
    
    In [15]: next(f)
    None
    Out[15]: 3
    
    In [16]: next(f)
    None
    Out[16]: 4
    
    In [17]: next(f)
    None
    ---------------------------------------------------------------------------
    StopIteration                             Traceback (most recent call last)
    <ipython-input-17-468f0afdf1b9> in <module>()
    ----> 1 next(f)
    
    StopIteration:
    

    使用__next__()メソッド(あまり使用されません)
    In [18]: f = gen()
    
    In [19]: f.__next__()
    Out[19]: 0
    
    In [20]: f.__next__()
    None
    Out[20]: 1
    
    In [21]: f.__next__()
    None
    Out[21]: 2
    
    In [22]: f.__next__()
    None
    Out[22]: 3
    
    In [23]: f.__next__()
    None
    Out[23]: 4
    
    In [24]: f.__next__()
    None
    ---------------------------------------------------------------------------
    StopIteration                             Traceback (most recent call last)
    <ipython-input-24-39ec527346a9> in <module>()
    ----> 1 f.__next__()
    
    StopIteration: