python学習ノート--理解ジェネレータ

15823 ワード

pythonを勉強しているうちにジェネレータ(generator)という概念に触れ始めたばかりの頃は、実はあまり理解できず、感覚が完全に把握されていなかったのですが、今日この文章を見たとき、この概念について本当にもっと理解したような気がしました.感覚生成器とリスト解析の関係はrangeとxrange関数の関係に少し似ているようです.リスト解析は、処理するシーケンスをすべて作成し、ジェネレータは複数回反復してシーケンス全体を生成します.そうしないと、実行するたびに1つだけ生成されます.また、関数にはyieldキーワードが表示されます.この関数がジェネレータ関数です.
この文章はPythonのジェネレータを深く理解し、廖雪峰先生のチュートリアルも見ることができます.
ジェネレータコンセプトジェネレータは、結果を一連に保存するのではなく、ジェネレータの状態を保存し、StopIteration異常が発生するまで反復するたびに値を返します.
ジェネレータ構文1.ジェネレータ式:通リスト解析構文は,リスト解析の[]を()ジェネレータ式に置き換えることでリスト解析がほぼ可能であるが,処理が必要なシーケンスが大きい場合にはリスト解析が比較的メモリを消費する.
>>> gen = (x**2 for x in range(5))
>>> gen
<generator object <genexpr> at 0x0000000002FB7B40>
>>> for g in gen:
...   print(g, end='-')
...
0-1-4-9-16-
>>> for x in [0,1,2,3,4,5]:
...   print(x, end='-')
...
0-1-2-3-4-5-
  • ジェネレータ関数:関数にyieldキーワードが表示されると、その関数は通常の関数ではなく、ジェネレータ関数になります.しかし、ジェネレータ関数は無線のシーケンスを生成することができ、リストは処理できません.yieldの役割は、1つの関数をgeneratorにすることであり、yieldを持つ関数は通常の関数ではなく、Python解釈器はgeneratorと見なします.

  • 次に、奇数を無限に生産できるジェネレータ関数を示します.
    def odd():
        n=1
        while True:
            yield n
            n+=2
    odd_num = odd()
    count = 0
    for o in odd_num:
        if count >=5: break
        print(o)
        count +=1

    もちろん反復器を手動で書くことで同様の効果が得られるが,生成器はより直感的で分かりやすい
    class Iter:
        def __init__(self):
            self.start=-1
        def __iter__(self):
            return self
        def __next__(self):
            self.start +=2 
            return self.start
    I = Iter()
    for count in range(5):
        print(next(I))

    余談:ジェネレータにはiter()メソッドとnext()メソッドが含まれているので、StopIterationを含むカスタムIterを含まずにforを直接使用して反復することができます.手動ループでのみ反復できます.
    >>> from collections import Iterable
    >>> from collections import Iterator
    >>> isinstance(odd_num, Iterable)
    True
    >>> isinstance(odd_num, Iterator)
    True
    >>> iter(odd_num) is odd_num
    True
    >>> help(odd_num)
    Help on generator object:
    
    odd = class generator(object)
     |  Methods defined here:
     |
     |  __iter__(self, /)
     |      Implement iter(self).
     |
     |  __next__(self, /)
     |      Implement next(self).

    ......上記の結果を見て、今はIteratorのやり方で循環できる自信があるでしょう!
    forループが実行されると、ループのたびにfab関数内部のコードが実行され、yield bに実行されるとfab関数は反復値を返し、次回の反復ではyield bの次の文からコードが実行され続け、関数のローカル変数は前回の実行中断前と全く同じように見え、関数は再びyieldに遭遇するまで実行され続けます.関数が正常に実行されている間にyieldによって数回中断されたように見え、中断するたびにyieldを通じて現在の反復値が返されます.
    yieldとreturnはジェネレータにあり、returnがない場合は、デフォルトで関数が完了するまでStopIterationに戻ります.
    >>> def g1():
    ...     yield 1
    ...
    >>> g=g1()
    >>> next(g)    #     next(g) ,     yield     ,             。
    1
    >>> next(g)    #     yield            ,        ,    StopIteration  。
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    StopIteration
    >>>

    returnが発生した場合、実行中にreturnが発生した場合、StopIterationを直接投げ出して反復を終了します.
    >>> def g2():
    ...     yield 'a'
    ...     return
    ...     yield 'b'
    ...
    >>> g=g2()
    >>> next(g)    #        yield 'a'      。
    'a'
    >>> next(g)    #          return,    StopIteration  ,  yield 'b'         。
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    StopIteration

    return後に値を返す場合、この値はStopIteration異常の説明であり、プログラムの戻り値ではありません.ジェネレータはreturnを使用して値を返すことができません.
    >>> def g3():
    ...     yield 'hello'
    ...     return 'world'
    ...
    >>> g=g3()
    >>> next(g)
    'hello'
    >>> next(g)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    StopIteration: world    

    ジェネレータがサポートする方法
    >>> help(odd_num)
    Help on generator object:
    
    odd = class generator(object)
     |  Methods defined here:
     ......
     |  close(...)
     |      close() -> raise GeneratorExit inside generator.
     |
     |  send(...)
     |      send(arg) -> send 'arg' into generator,
     |      return next yielded value or raise StopIteration.
     |
     |  throw(...)
     |      throw(typ[,val[,tb]]) -> raise exception in generator,
     |      return next yielded value or raise StopIteration.

    close()はジェネレータ関数を手動で閉じ、後の呼び出しはStopIteration異常に直接戻ります.
    >>> def g4():
    ...     yield 1
    ...     yield 2
    ...     yield 3
    ...
    >>> g=g4()
    >>> next(g)
    1
    >>> g.close()
    >>> next(g)    #   ,yield 2 yield 3        
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    StopIteration

    send()ジェネレータ関数の最大の特徴は,外部から伝達された変数を受け入れ,変数内容に基づいて結果を計算して返すことである.これはジェネレータ関数が最も理解しにくい場所であり、最も重要な場所でもあり、後でお話しする協程を実現するのはすべてそれに頼っています.
    def gen():
        value=0
        while True:
            receive=yield value
            if receive=='e':
                break
            value = 'got: %s' % receive
    
    g=gen()
    print(g.send(None))     
    print(g.send('aaa'))
    print(g.send(3))
    print(g.send('e'))

    プロセスの実行:
  • は、g.send(None)またはnext(g)によってジェネレータ関数を起動し、最初のyield文の終了位置まで実行することができる.このときyield文は実行されましたが、receiveには値が割り当てられていません.yield valueは初期値0を出力します.注意:ジェネレータ関数を起動するときはsend(None)しか使用できません.他の値を入力しようとするとエラーメッセージが表示されます.
  • g.send(‘aaa’)を介してaaaが入力され、receiveに値が割り当てられ、valueの値が計算され、whileヘッダに戻り、yield value文の実行が停止します.このときyield valueは「got:aaa」を出力し、掛けます.
  • g.send(3)を通過すると、2ステップ目が繰り返され、最終出力結果は「got:3」
  • となる.
  • g.send(‘e’)では、プログラムはbreakを実行してループを押し出し、最後に関数全体が実行されるので、StopIteration異常が得られます.最後の実行結果は、
  • です.
    0
    got: aaa
    got: 3
    Traceback (most recent call last):
    File "h.py", line 14, in <module>
      print(g.send('e'))
    StopIteration

    throw()は、システム定義の例外、またはカスタムの例外を終了するジェネレータ関数に例外を送り込むために使用されます.throw()後に異常を直接飛び出してプログラムを終了したり、yieldを1つ消費したり、次のyieldがないときにプログラムの最後に直接行ったりします.
    def gen():
        while True: 
            try:
                yield 'normal value'
                yield 'normal value 2'
                print('here')
            except ValueError:
                print('we got ValueError here')
            except TypeError:
                break
    
    g=gen()
    print(next(g))
    print(g.throw(ValueError))
    print(next(g))
    print(g.throw(TypeError))

    出力結果:
    normal value
    we got ValueError here
    normal value
    normal value 2
    Traceback (most recent call last):
      File "h.py", line 15, in <module>
        print(g.throw(TypeError))
    StopIteration

    説明:
  • print(next(g):normal valueが出力され、yield「normal value 2」の前にとどまります.
  • g.throw(ValueError)が実行されているため、後続のtry文はすべてスキップされます.つまりyield‘normal value 2’は実行されず、except文に入りwe got ValueErrorhereが印刷されます.次にwhile文セクションに再び進み、yieldを消費するのでnormal valueが出力されます.
  • print(next(g))は、yield‘normal value 2’文を実行し、その文が実行された後の位置にとどまる.
  • g.throw(TypeError):try文が飛び出し、print(‘here’)が実行されないようになり、break文が実行され、whileループが飛び出し、プログラムの最後に達するため、StopIteration異常が飛び出します.以下に、多次元リストを展開するための総合例、すなわち、多次元リストを扁平化するための
  • を示す.
    def flatten(nested):
    
        try:
            #      ,      TypeError。
            if isinstance(nested, str):
                raise TypeError
            for sublist in nested:
                #yield flatten(sublist)
                for element in flatten(sublist):
                    #yield element
                    print('got:', element)
        except TypeError:
            #print('here')
            yield nested
    
    L=['aaadf',[1,2,3],2,4,[5,[6,[8,[9]],'ddf'],7]]
    for num in flatten(L):
        print(num)

    理解が難しい場合はprint文のコメントを開いて見ておくとわかります.
    まとめ
  • アヒルモデル理論によれば、ジェネレータはforを用いて反復することができる反復器である.
  • 初めてnext(generator)を実行するとyield文が実行された後にプログラムが保留され、すべてのパラメータとステータスが保存されます.もう一度next(generator)を実行すると、保留中の状態から後に実行されます.プログラムの最後に遭遇した場合、またはStopIterationに遭遇した場合、ループは終了します.
  • はgeneratorを通過することができる.send(arg)はパラメータを伝達し,これはコヒーレントモデルである.
  • はgeneratorを通過することができる.throw(exception)は例外を送信します.throw文はyieldを消費します.generatorでいいです.close()を使用して、ジェネレータを手動で閉じます.
  • next()はsend(None)
  • に等価である.