Pythonジェネレータ関数の深入浅出)

117506 ワード

Pythonジェネレータ関数の深入浅出
  • 1、ジェネレータコンセプト
  • 1.1`yield from`文法
  • 1.1.1例1
  • 1.1.2例2

  • 2、ジェネレータ関数
  • 2.1例
  • 3、ジェネレータ関数の実行
  • 3.1ジェネレータ関数の実行順序
  • は、次の例によって理解する.
  • 3.1.1例1
  • 3.1.2例2
  • 3.1.3例3
  • 3.1.4例4
  • 3.1.5例5
  • 3.1.6例6
  • 3.1.7例7
  • 3.1.8例8
  • 3.1.9例9

  • 4、ジェネレータアプリケーション
  • 4.1無限ループ
  • 4.2カウンタ
  • 4.3ジェネレータインタラクション
  • 4.3.1リセット機能付きカウンタ1
  • 4.3.2リセット機能付きカウンタ2
  • 4.3.3リセット機能付きカウンタ3
  • 4.4 Coroutine

  • 1、ジェネレータの概念
  • ジェネレータは、ジェネレータオブジェクトを指す、ジェネレータ式で得ることもできるし、yieldキーワードを用いてジェネレータ関数
  • を得ることもできる.
  • ジェネレータオブジェクト、反復可能オブジェクト、反復器
  • ジェネレータオブジェクトは、遅延計算、不活性評価の
  • である.
  • 興味があるのは、以下のリンクPythonのリスト/集合/辞書解析式、ジェネレータ式、反復器、反復可能オブジェクトの奥行き
  • を参照することができる.
    1.1 yield from構文
  • Python 3.3からyield from文法
  • が追加されました.
  • yield from iterablefor item in iterable: yield
  • に等価である.
  • yield fromは文法を簡略化する文法糖
  • である.
  • 本質的にyield fromとは、fromの後の反復可能なオブジェクトから要素を1つずつyieldずつ取り出して
  • を意味する.
  • は、以下の関数から実行プロセス全体、特にforが外部の実行文を循環する順序
  • を見る.
    1.1.1例1
    def inc():
        print('Before for')
        for x in range(3):
            print('Before yield')
            yield x
            print('After yield')
        print('After for')
        
    foo = inc()
    
    for i in foo:
        print(i)
    
    Before for
    Before yield
    0
    After yield
    Before yield
    1
    After yield
    Before yield
    2
    After yield
    After for
    

    1.1.2例2
    def inc():
        print('Before yield')
        yield from range(3)
        print('After yield')
        
    foo = inc()
    
    for i in foo:
        print(i)
    
    Before for & yield
    0
    1
    2
    After for & yield
    

    2、ジェネレータ関数
  • 関数体にyield文を含む関数がジェネレータ関数であり、呼び出すとジェネレータオブジェクト
  • に戻る.
  • 普通の関数は呼び出して、関数は直ちに実行して
  • を実行することを知っています
  • ジェネレータ関数呼び出しは、直ちに関数体を実行するのではなく、next関数を使用してジェネレータ関数実行後に得るジェネレータオブジェクト
  • を駆動する必要がある.
  • ジェネレータ式とジェネレータ関数はいずれもジェネレータオブジェクトを得ることができるが、ジェネレータ関数はより複雑な論理
  • を書くことができる.
    2.1例
    def inc():
        for i in range(3):
            yield i
    
    print(type(inc))
    print(type(inc()))
    
    gen1 = inc()
    print(type(gen1))
    
    for x in gen1:
        print(x)
    print(1, '=' * 55)
    for y in gen1:   # for           
        print(y)
    print(2, '*' * 55)
    next(gen1)   # next           ,    StopIteration 
    
    <class 'function'>
    <class 'generator'>
    <class 'generator'>
    0
    1
    2
    1 =======================================================
    2 *******************************************************
    ---------------------------------------------------------------------------
    StopIteration                             Traceback (most recent call last)
    <ipython-input-8-04af5606ee5b> in <module>
         15     print(y)
         16 print(2, '*' * 55)
    ---> 17 next(gen1)   # next     StopIteration
    
    StopIteration: 
    

    3、ジェネレータ関数の実行
  • ジェネレータ関数では、yieldを複数回行うことができ、yieldを1回実行するたびに実行を一時停止し、yield式の値を
  • に戻す.
  • が再実行すると、次のyield文に実行され、
  • の実行が一時停止する.
  • return文は依然として関数の実行を終了することができるが、return文の戻り値は
  • に取得できない.
  • rerurnは、現在の関数が戻る、実行を続行できない、次の値を取得し続けることができず、StopIteration異常
  • を投げ出す.
  • 関数に表示するreturn文がない場合、ジェネレータ関数が最後まで実行する(return Noneが実行されたことに相当)場合、StopIteration異常
  • が放出される.
  • next(generator)は、関数の現在位置から後に遭遇する最初のyield文に戻る、値がポップアップされ、関数実行
  • が一時停止する.
  • は、nextの関数を再び実行し、上述の処理手順
  • と同様である.
  • nextの関数を呼び出し続け、ジェネレータ関数が実行を終了すると(明示的または暗黙的にreturnの文が呼び出された)、StopIterationの異常
  • が投げ出す.
  • next( function, 'DefaultValue' )ジェネレータ関数に戻り値がない場合、エラーは報告されず、デフォルト値
  • がポップアップされます.
    3.1ジェネレータ関数の実行順序については、次の例を参照してください.
    3.1.1例1
    #               
    def inc():
        print('+++ Before yield +++')
        yield from range(3)
        print('+++ After  yield +++')
    print('===================')    
    gen = inc()
    print('===================')   
    for x in gen:
        print('I am in For Function.')
        print(x)
    
    ===================
    ===================
    +++ Before yield +++
    I am in For Function.
    0
    I am in For Function.
    1
    I am in For Function.
    2
    +++ After  yield +++
    

    3.1.2例2
    #               
    def inc():
        for i in range(3):
            print('+++ Before yield +++')
            yield i
            print('+++ After  yield +++')
            
    print('===================')    
    gen = inc()
    print('===================') 
    print('+++++++++++++++++++') 
    print(next(gen))
    print('+++++++++++++++++++') 
    for x in gen:
        print('I am in For Function.')
        print(x)
    
    ===================
    ===================
    +++++++++++++++++++
    +++ Before yield +++
    0
    +++++++++++++++++++
    +++ After  yield +++
    +++ Before yield +++
    I am in For Function.
    1
    +++ After  yield +++
    +++ Before yield +++
    I am in For Function.
    2
    +++ After  yield +++
    

    3.1.3例3
    def gen():
        print('Line 1')
        yield 1
        print('Line 2')
        yield 2
        print('Line 3')
        return 3
        yield 4
        
    print(1, next(gen()))
    print(2, next(gen()))
    
    g = gen()
    
    print(3, next(g))
    print(4, next(g))
    print(5, next(g, 'End'))  #           
    print(6, next(g))
    
    Line 1
    1 1
    Line 1
    2 1
    Line 1
    3 1
    Line 2
    4 2
    Line 3
    5 End
    ---------------------------------------------------------------------------
    StopIteration                             Traceback (most recent call last)
    <ipython-input-11-c14b5e4a2576> in <module>
         16 print(4, next(g))
         17 print(5, next(g, 'End'))
    ---> 18 print(6, next(g))
    
    StopIteration: 
    

    3.1.4例4
    def inc():
        def counter():
            i = 0
            while True:
                i += 1
                print('I am in counter_function. Before yield.', i)
                yield i
                print('I am in counter_function. After yield.', i)
        c = counter()
        print('I am in inc_function.', id(c))
        def inner():
            print('I am in inner_function.',id(c))
            return next(c)  # c      ,    
        return inner    # return lambda : next(c)
    g = inc()
    print('1 >>>', g(), id(g))
    print('=' * 55)
    print('2 >>>', g(), id(g))
    print('*' * 55)
    print('3 >>>', g(), id(g))
    def inc():
        def counter():
            i = 0
            while True:
                i += 1
                print('I am in counter_function. Before yield.', i)
                yield i
                print('I am in counter_function. After yield.', i)
        c = counter()
        print('I am in inc_function.', id(c))
        def inner():
            print('I am in inner_function.',id(c))
            return next(c)  # c      ,    
        return inner    # return lambda : next(c)
    g = inc()
    print('1 >>>', g(), id(g))
    print('=' * 55)
    print('2 >>>', g(), id(g))
    print('*' * 55)
    print('3 >>>', g(), id(g))
    
    I am in inc_function. 83441736
    I am in inner_function. 83441736
    I am in counter_function. Before yield. 1
    1 >>> 1 83500776
    =======================================================
    I am in inner_function. 83441736
    I am in counter_function. After yield. 1
    I am in counter_function. Before yield. 2
    2 >>> 2 83500776
    *******************************************************
    I am in inner_function. 83441736
    I am in counter_function. After yield. 2
    I am in counter_function. Before yield. 3
    3 >>> 3 83500776
    

    3.1.5例5
    def counter():
        count = 0
        while True:
            count += 1
            yield count
    
    def inc(c):
        print(c, id(c))
        return next(c)
    c = counter()
    print(c)
    print(inc(c))
    print(inc(c))
    print(inc(c))
    
    <generator object counter at 0x0000000004F93D48>
    <generator object counter at 0x0000000004F93D48> 83443016
    1
    <generator object counter at 0x0000000004F93D48> 83443016
    2
    <generator object counter at 0x0000000004F93D48> 83443016
    3
    

    3.1.6例6
    def counter():
        count = 0
        while True:
            count += 1
            yield count
    
    def inc(c = counter()):  #                 counter()
        return next(c)
    print(inc.__defaults__)   # tuple              
    print(11111, inc(), id(inc()), sep=' ||||| ')
    print(22222, inc(), id(inc()), sep=' ||||| ')
    print(33333, inc(), id(inc()), sep=' ||||| ')
    print(inc.__defaults__)
    
    #              ,      
    #         inc()        
    (<generator object counter at 0x0000000004F935C8>,)
    11111 ||||| 1 ||||| 8791271174448
    22222 ||||| 3 ||||| 8791271174512
    33333 ||||| 5 ||||| 8791271174576
    (<generator object counter at 0x0000000004F935C8>,)
    

    3.1.7例7
  • と例7を比較して理解する
  • def inc():
        def counter():
            count = 0
            while True:
                count += 1
                yield count
        
        c = counter()
        
        return next(c)
    
    g = inc()
    print(g)
    print(g())
    
    1
    ---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    <ipython-input-24-7a9ff0468485> in <module>
         12 g = inc()
         13 print(g)
    ---> 14 print(g())
    
    TypeError: 'int' object is not callable
    

    3.1.8例8
  • と例8を比較して理解する
  • def inc():
        def counter():
            count = 0
            while True:
                count += 1
                yield count
        
        c = counter()  #      
        def fn():
            return next(c)
        return fn  #       return lambda : next(c)
    
    g = inc()
    print(g, inc)
    print(g())
    print(g())
    print(g())
    
    <function inc.<locals>.fn at 0x0000000008BE4318> <function inc at 0x0000000008BE4D38>
    1
    2
    3
    

    3.1.9例9
  • ジェネレータ関数を用いてフィボナッチ数列を計算し、割り込み条件
  • を構成する.
    def fib(n):
        x = 0
        y = 1
        count = 0
        while True:
            x, y = y, x + y
            count += 1
            if n <= count:
                yield x
                break
    foo = fib(10)
    print(foo)
    for i in foo:
        print(i)
    print(foo)
    next(foo)  #           ,  
    
    <generator object fib at 0x0000000008B5E348>
    55
    <generator object fib at 0x0000000008B5E348>
    ---------------------------------------------------------------------------
    StopIteration                             Traceback (most recent call last)
    <ipython-input-35-10876317642f> in <module>
         14     print(i)
         15 print(foo)
    ---> 16 next(foo)
    
    StopIteration: 
    
    def fib():
        x = 0
        y = 1
        while True:
            yield y
            x, y = y, x + y
    
    foo = fib()
    
    for i in range(4):
        print(next(foo))
    
    1
    1
    2
    3
    

    4、ジェネレータ応用
    4.1無限ループ
    def counter():
        i = 0
        while True:
            i += 1
            yield i
    c = counter()
    print(next(c))
    print(next(c))
    
    1
    2
    

    4.2カウンタ
    def inc():
        def counter():
            i = 0
            while True:
                i += 1
                yield i
        
        c = counter()
        
        def inner():
            return next(c)
        return inner  # return lambda : next(c)
    
    foo = inc()
    for i in range(3):
        print(foo())
    
    1
    2
    3
    

    4.3ジェネレータインタラクション
  • Pythonは、ジェネレータオブジェクトと対話可能な方法sendを提供する.
  • sendメソッドを呼び出し、sendの実パラメータをyield文に伝えることができ、この結果は等式右側で他の変数
  • に付与することができる.
  • sendは、nextと同様にジェネレータの起動を推進する、
  • を実行することができる.
    4.3.1リセット機能付きカウンタ1
    #          
    
    def inc():
        print('-' * 55)
        def counter():
            i = 0
            while True:
                i += 1
                print('*' * 55)
                #            ,   response,               
                #          ,      
                if i > 1:  
                    print('Before yield, response is {}.'.format(response))
                response = yield i
                print('After yield, response is {}.'.format(response))
                if response is not None:
                    print('=' * 55)
                    i = response
        c = counter()
        return lambda x=False: next(c) if not x else c.send(0)
    
    foo = inc()
    print(foo())
    print(foo())
    print(foo())
    print(foo(True))
    print(foo())
    print(foo())
    
    -------------------------------------------------------
    *******************************************************
    1
    After yield, response is None.
    *******************************************************
    Before yield, response is None.
    2
    After yield, response is None.
    *******************************************************
    Before yield, response is None.
    3
    After yield, response is 0.
    =======================================================
    *******************************************************
    1
    After yield, response is None.
    *******************************************************
    Before yield, response is None.
    2
    After yield, response is None.
    *******************************************************
    Before yield, response is None.
    3
    

    4.3.2リセット機能付きカウンタ2
    #          
    
    def inc():
        print('-' * 55)
        def counter():
            i = 0
            while True:
                i += 1
                print('*' * 55)
                if i > 1:
                    print('Before yield, response is {}.'.format(response))
                response = yield i
                print('After yield, response is {}.'.format(response))
                if response is not None:
                    print('=' * 55)
                    i = response
        c = counter()
        
        def _inc(x=False):
            if x == True:
                return c.send(0)  #next
            else:
                return next(c)
        return _inc
    
    #    return lambda x=False: next(c) if not x else c.send(0)
    
    foo = inc()
    print(foo())
    print(foo())
    print(foo())
    print(foo(True))
    print(foo())
    print(foo())
    
    -------------------------------------------------------
    *******************************************************
    1
    After yield, response is None.
    *******************************************************
    Before yield, response is None.
    2
    After yield, response is None.
    *******************************************************
    Before yield, response is None.
    3
    After yield, response is 0.
    =======================================================
    *******************************************************
    1
    After yield, response is None.
    *******************************************************
    Before yield, response is None.
    2
    After yield, response is None.
    *******************************************************
    Before yield, response is None.
    3
    

    4.3.3リセット機能付きカウンタ3
    #          
    def counter():
        count = 0
        while True:
            count += 1
            response = yield count
            print(response, '======')
            if response is not None and isinstance(response, int):
                count = response
    c = counter()
    print(next(c))
    print(next(c))
    print(c.send(0))
    print(next(c))
    print(next(c))
    
    1
    None ======
    2
    0 ======
    1
    None ======
    2
    None ======
    3
    

    4.4協程Coroutine
  • ジェネレータの高度な使い方
  • プロセス、スレッドより軽量であり、ユーザ空間スケジューリング関数の実装
  • である.
  • Python 3 asyncioはコラボレーション実装であり、標準ライブラリ
  • に追加されている.
  • Python 3.5 async、awaitキーワードを使用した直接オリジナルサポートコラボレーション
  • コパスは非プリエンプトスケジューリング
  • である.
  • 連携スケジューリング実現構想
           A B
    next(A) ,A    yieldnext(B)
    B   next(B)next(A)