Pythonステップアップ_ジェネレータ&ジェネレータ式


目次
  • ディレクトリ
  • 関連知識点
  • ジェネレータ
  • ジェネレータfabの実行プロセス
  • ジェネレータと反復器の違い
  • ジェネレータの利点
  • 強化ジェネレータ特性
  • ジェネレータ式
  • ジェネレータ式サンプル
  • 小結
  • 関連知識点
    Pythonステップアップ_反復器&リスト解析
    ビルダー
    yieldキーワードを持つ関数をPythonではgenerator(ジェネレータ)と呼ぶ.Python解釈器はyieldキーワードを持つ関数をgeneratorとして処理します.1つの関数またはサブルーチンは1回しかreturnできませんが、1つのジェネレータは実行を一時停止し、中間の結果を返すことができます.これがyield文の機能です.中間値を呼び出し者に返し、実行を一時停止します.
    EXAMPLE:
    In [94]: def fab(max):
        ...:     n, a, b = 0, 0, 1
        ...:     while n < max:
        ...:         yield b
        ...:         a, b = b, a + b
        ...:         n = n + 1
        ...:
    
    In [95]: f = fab(5)
    
    In [96]: f.next()
    Out[96]: 1
    
    In [97]: f.next()
    Out[97]: 1
    
    In [98]: f.next()
    Out[98]: 2
    
    In [99]: f.next()
    Out[99]: 3
    
    In [100]: f.next()
    Out[100]: 5
    
    In [101]: f.next()
    ---------------------------------------------------------------------------
    StopIteration                             Traceback (most recent call last)
    101-c3e65e5362fb> in ()
    ----> 1 f.next()
    
    StopIteration:

    ジェネレータfab()の実行プロセス
    f = fab(5)を実行すると、fab()関数のコードブロックがすぐに実行されるのではなく、まずiterableオブジェクトが返されます.forループ文が実行されると、fab()関数のコードブロックが実行されます.文yield bに実行されると、fab()関数は反復値を返し、次回の反復前にプログラムフローはyield bの次の文に戻って実行を続け、終了するまで再びforループに戻ります.1つの関数が通常の実行中にyieldによって数回中断されたように見え、中断のたびにyieldを通じて現在の反復値が返されます.これにより、ジェネレータは、キーワードyieldによって反復器をメモリに戻して処理し続け、オブジェクトを一度にメモリにすべて入れることなく、メモリ領域を節約することができる.この点からジェネレータと反復器はよく似ていますが、もっと深く理解すれば、両者には違いがあります.
    ジェネレータと反復器の違い
    ジェネレータのもう1つの利点は、反復中のすべての要素を事前に準備する必要がないことです.つまり、オブジェクトのすべての要素をメモリに保存する必要がなく、操作を開始することです.ジェネレータは、エレメントを反復した場合にのみメモリにエレメントを格納しますが、その前または後にエレメントが存在しないか破棄されます.この特徴は、巨大または無限のクラスシーケンスオブジェクト、EG.大ファイル/大集合/大辞書/フィボナッチ数列などを遍歴するのに特に適している.この特徴は遅延計算や不活性評価(Lazy evaluation)と呼ばれ,メモリを効率的に節約できる.不活性評価は実際には協同プログラムの思想である.
    コラボレーション・プログラム:独立して実行できる関数呼び出しです.この呼び出しは一時停止または一時停止され、その後、プログラム・フローが一時停止された場所から続行または再開されます.Pythonは、協同プログラムが保留されると、その協同プログラムから中間状態にある属性の戻り値(yieldによって返される)を取得することができ、next()メソッドを呼び出して協同プログラムにフローを戻すと、追加のパラメータまたは変更されたパラメータを入力し、前回保留された次の文から実行を継続することができる.これは、プロセス中断に似た関数呼び出し方式です.このような保留関数呼び出しが属性中間値を返した後も複数回実行可能な協同プログラムをジェネレータと呼ぶ.
    NOTE:反復器は上記の特性を持たず、いくつかの巨大なクラスシーケンスオブジェクトを処理するのに適していないので、ジェネレータを使用して反復シーンを処理することを優先することをお勧めします.
    ジェネレータのメリット
    以上のように、ジェネレータを使用する最良のシーンは、巨大なデータセットを反復的に通り抜ける必要がある場合です.たとえば、巨大なファイル/複雑なデータベースクエリーなどです.
    EXAMPLE 2:大きなファイルを読み込む
    def read_file(fpath): 
        BLOCK_SIZE = 1024 
        with open(fpath, 'rb') as f: 
            while True: 
                block = f.read(BLOCK_SIZE) 
                if block: 
                    yield block 
                else: 
                    return

    ファイルオブジェクトにread()メソッドを直接呼び出すと、予測不可能なメモリ消費量が発生します.良い方法は、固定長のバッファを利用して、ファイルの一部の内容を絶えず読み取ることです.yieldにより、読み取りファイルの反復クラスを記述する必要がなくなり、ファイル読み取りを簡単に実現できます.
    ビルダーの機能の強化next()メソッドを使用して次の生成値を取得することができるほか、ユーザはsend()メソッドを使用して、新しい値または修正された値をジェネレータに返すこともできる.これに加えて、close()メソッドを使用して、ジェネレータをいつでも終了することもできます.EXAMPLE 3:
    In [5]: def counter(start_at=0):
       ...:     count = start_at
       ...:     while True:
       ...:         val = (yield count)
       ...:         if val is not None:
       ...:             count = val
       ...:         else:
       ...:             count += 1
       ...:
    
    In [6]: count = counter(5)
    
    In [7]: type(count)
    Out[7]: generator
    
    In [8]: count.next()
    Out[8]: 5
    
    In [9]: count.next()
    Out[9]: 6
    
    In [10]: count.send(9)           #               yield count
    Out[10]: 9
    
    In [11]: count.next()
    Out[11]: 10
    
    In [12]: count.close()          #        
    
    In [13]: count.next()
    ---------------------------------------------------------------------------
    StopIteration                             Traceback (most recent call last)
    13-3963aa0a181a> in ()
    ----> 1 count.next()
    
    StopIteration:

    ジェネレータ式
    ジェネレータ式は、前述したように、リスト解析の拡張です.ジェネレータは特定の関数であり、中間値を返し、コードの実行を保留し、後で実行を再開できます.リスト解析の不足は、リストオブジェクトを作成するためにすべてのデータを一度に生成する必要があるため、大量のデータを反復するのに適していないことです.ジェネレータ式は、リスト解析とジェネレータを組み合わせてこの問題を解決します.
  • リスト解析[expr for iter_var in iterable if cond_expr]
  • ジェネレータ式(expr for iter_var in iterable if cond_expr)
  • 両者の構文は非常に似ていますが、ジェネレータ式はリストタイプのオブジェクトではなく、メモリが使いやすい構造のジェネレータオブジェクトを返します.
    ジェネレータ式サンプル
    ファイル内で最も長いローを検索する機能の実装を改善することで、ジェネレータの利点を見てみましょう.EXAMPLE 4:変数longestにループを介してより長い行を割り当てる比較的一般的な方法.
    f = open('FILENAME', 'r')
    longest = 0
    while True:
        linelen = len(f.readline().strip())
        if not linelen:
            break
        if linelen > longest:
            longest = linelen
    f.close()
    
    return longest

    明らかに、この例では、反復が必要なオブジェクトはファイルオブジェクトです.
    改善1:ファイルのすべてのローを読み込む場合は、できるだけ早くファイルリソースを解放する必要があります.たとえば、1つのログ・ファイルでは、多くの異なるプロセスが操作されるため、いずれかのプロセスがこのファイルのハンドルを持って放さないことは許されません.
    f = open('FILENAME', 'r')
    longest = 0
    allLines = f.readlines()
    f.close()
    for line in allLines:
        linelen = len(line.strip())
        if not linelen:
            break
        if linelen > longest:
            longest = linelen
    
    return longest

    改善2:リスト解析を使用して、allLinesのすべてのローのリストを取得するときに各ローを処理するなど、上記のコードを簡略化できます.
    f = open('FILENAME', 'r')
    longest = 0
    allLines = [x.strip() for x in f.readlines()]
    f.close()
    for line in allLines:
        linelen = len(line)
        if not linelen:
            break
        if linelen > longest:
            longest = linelen
    
    return longest

    改善3:大きなファイルを処理する場合、file.readlines()は賢明な選択ではありません.readlines()はファイル内のすべてのローを読み出すからです.では、すべてのローのリストを取得する方法はありますか?fileファイルに内蔵された反復器を適用できます.
    f = open('FILENAME', 'r')
    allLinesLen = [line(x.strip()) for x in f]
    f.close()
    return max(allLinesLen)   #           

    ループ比較を使用して現在の最大値を保持する方法で処理する必要はありません.すべての行の長さの最後の要素をリストオブジェクトに保存し、大きな値を取得すればいいです.
    改善4:ここでは、リスト解析を使用してfileオブジェクトを処理すると、fileのすべての行がメモリに読み込まれ、新しいリストオブジェクトが作成されるという問題があります.これはメモリが友好的ではない実装方法です.では、リスト解析の代わりにジェネレータ式を使用できます.
    f = open('FILENAME', 'r')
    allLinesLen = (line(x.strip()) for x in f)   #     x     yield x
    f.close()
    return max(allLinesLen)

    関数でジェネレータ式をパラメータとして使用する場合、カッコ'()'を無視できるため、コードをさらに簡略化できます.
    f = open('FILENAME', 'r')
    longest = max(line(x.strip()) for x in f)
    f.close()
    return longest

    最後に、この機能を1行のコードで実現し、Python解析器に開いているファイルを処理させることができます.もちろん、コードが少なければ少ないほど良いというわけではありません.例えば、次の行のコードはループごとにopen()関数を呼び出し、効率的には4以上改善されていません.
    return max(line(x.strip()) for x in open('FILENAME'))

    小結
    反復してオブジェクトを通り抜ける必要がある場合は、反復器の代わりにジェネレータを使用し、リスト解析の代わりにジェネレータ式を使用することを優先的に考慮する必要があります.もちろんこれは絶対ではありません.反復器とジェネレータはPythonの重要な特性であり,よりPythonicのコードを書くことができることをよく理解している.