python generators

12968 ワード

この文章はここから訳す
次の記事を振り返ってpythonでiteratorを作成することを議論します.iteratorを作成するには、有_を実装する必要があります.iter__()と_next__()メソッドのクラス、クラスは内部状態を追跡することができ、要素が戻ってこないときにStopIteration異常を引き起こす.
この過程は煩雑で直感に反する.Generatorはこの問題を解決することができる.
python generatorはiteratorを簡単に作成する方法です.前に述べた煩雑な手順はgeneratorによって自動的に完成することができる.
簡単に言えば、generatorは反復器のオブジェクトを返すことができる関数である.
python generatorを作成するにはどうすればいいですか?
関数を作成するように簡単ですが、return宣言ではなくyield宣言を使用します.
1つの関数が少なくとも1つのyield宣言を含む場合(もちろん他のyieldまたはreturnを含むこともできる)、それはgeneratorである. 
yieldとreturnは関数にいくつかのものを返します.違いは、return宣言は完全に関数を終了し、yield宣言は関数を一時停止し、そのすべての状態を保存し、その後呼び出された後も実行し続けることです.
generator関数と一般関数の違い
  • generator関数は、1つ以上のyield宣言
  • を含む.
  • generator関数が呼び出されるとiteratorオブジェクトが返されますが、関数はすぐに
  • の実行を開始しません.
  • __iter__()と_next__()メソッドは自動的に実装されるので、next()関数を使用して、返されるこのiteratorオブジェクトを
  • 反復することができる.
  • generatorがyield文に実行するとgenerator関数は一時停止し、プログラム制御フローは呼び出し元
  • に移行する.
  • generatorに対する連続呼び出しの間にgeneratorのローカル変数とステータスが
  • 保存される.
  • 最終的にgenerator関数は終了し、再びgeneratorを呼び出すとStopIteration異常
  • が発生する.
    次の例では、上記のすべてのポイントを説明します.myという名前があります.gen()の関数で、yield宣言があります.
    # A simple generator function
    def my_gen():
        n = 1
        print('This is printed first')
        # Generator function contains yield statements
        yield n
    
        n += 1
        print('This is printed second')
        yield n
    
        n += 1
        print('This is printed at last')
        yield n
    インタラクションでインタラクションした結果は、次のとおりです.
    >>> # It returns an object but does not start execution immediately.
    >>> a = my_gen()
    
    >>> # We can iterate through the items using next().
    >>> next(a)
    This is printed first
    1
    >>> # Once the function yields, the function is paused and the control is transferred to the caller.
    
    >>> # Local variables and theirs states are remembered between successive calls.
    >>> next(a)
    This is printed second
    2
    
    >>> next(a)
    This is printed at last
    3
    
    >>> # Finally, when the function terminates, StopIteration is raised automatically on further calls.
    >>> next(a)
    Traceback (most recent call last):
    ...
    StopIteration
    >>> next(a)
    Traceback (most recent call last):
    ...
    StopIteration
    興味深いことに、この例では変数nが呼び出されるたびに記憶される.一般的な関数とは異なり、関数yield以降のローカル変数は破棄されず、generatorオブジェクトはこのように反復するしかありません.
    上記の手順を繰り返すには、a=my_のようなものが必要です.gen()は、別のgeneratorオブジェクトを作成し、nextメソッドを使用して反復します.
    注意:generatorオブジェクトに対してforループを直接使用できます.
    これは、forループがiteratorオブジェクトを受信し、next()関数を使用して反復し、StopIteration異常が発生した場合に自動的に停止するためです.pythonがforループをどのように実現しているかを確認するには、ここをクリックします.
    # A simple generator function
    def my_gen():
        n = 1
        print('This is printed first')
        # Generator function contains yield statements
        yield n
    
        n += 1
        print('This is printed second')
        yield n
    
        n += 1
        print('This is printed at last')
        yield n
    
    # Using for loop
    
    # Output: 
    # This is printed first
    # 1
    # This is printed second
    # 2
    # This is printed at last
    # 3
    
    for item in my_gen():
        print(item) 

    循環python generator
    上の例は実際の応用意義がなく,我々はただ背後の原理を探究するためである.
    通常、generatorはサイクルと結合して実現され、このサイクルは終了条件を有する.
    reverseの文字列の例を見てみましょう
    def rev_str(my_str):
        length = len(my_str)
        for i in range(length - 1,-1,-1):
            yield my_str[i]
    
    # For loop to reverse the string
    # Output:
    # o
    # l
    # l
    # e
    # h
    for char in rev_str("hello"):
         print(char)
    forループでrange()関数を使用して、逆順序のindexを取得します.
    generatorはstringに適用できるほか、list、tupleなどの他のタイプのiteratorにも適用できる.
    python generator式
    generator式を使用すると、簡単なgeneratorを簡単に作成できます.
    Lambda関数が匿名関数を作成できるように、generator関数は匿名generator関数を作成します.
    generator式の構文はpythonのlist comprehensionに似ていますが、角カッコが丸カッコに置き換えられているだけです.
    list comprehensionとgenerator式の主な違いは、前者はすべてのlistを生成し、後者は毎回1つしか生成しないことです.
    怠け者で、リクエストを受けたときだけ出力が発生します.したがって、generator式はlist comprehensionよりもメモリを節約します.
    # Initialize the list
    my_list = [1, 3, 6, 10]
    
    # square each term using list comprehension
    # Output: [1, 9, 36, 100]
    [x**2 for x in my_list]
    
    # same thing can be done using generator expression
    # Output:  at 0x0000000002EBDAF8>
    (x**2 for x in my_list)
    の上記の例では、generator式はすぐに必要な結果を生成するのではなく、itemを生成する必要があるときにgeneratorオブジェクトを返します.
    # Intialize the list
    my_list = [1, 3, 6, 10]
    
    a = (x**2 for x in my_list)
    # Output: 1
    print(next(a))
    
    # Output: 9
    print(next(a))
    
    # Output: 36
    print(next(a))
    
    # Output: 100
    print(next(a))
    
    # Output: StopIteration
    next(a)
    generator式は、関数の内部で使用できます.このように使用すると、カッコを捨てることができます.
    >>> sum(x**2 for x in my_list)
    146
    
    >>> max(x**2 for x in my_list)
    100

    pythonではなぜgeneratorを使うのですか?次のいくつかの理由でgeneratorが魅力的な選択になりました
    1.実現しやすい
    iteratorクラスに比べてgeneratorの実現は明確で簡潔である.次はiteratorで2を実現する指数関数です
    class PowTwo:
        def __init__(self, max = 0):
            self.max = max
    
        def __iter__(self):
            self.n = 0
            return self
    
        def __next__(self):
            if self.n > self.max:
                raise StopIteration
    
            result = 2 ** self.n
            self.n += 1
            return result
    generatorこのように実現
    def PowTwoGen(max = 0):
        n = 0
        while n < max:
            yield 2 ** n
            n += 1
    は、generatorが実装の詳細を自動的に追跡するため、より明確で簡潔である.
    2.メモリの節約
    1つの関数が1つのシーケンス(sequence)を返すと、メモリにこのシーケンスを構築して返します.このシーケンスに多くのデータが含まれていれば、過ぎたるは及ばざるがごとし.
    シーケンスがgenerator方式で実現されている場合、メモリは友好的です.彼は毎回1つのitemしか生成しないからです.
    3.無限のstreamを表す
    generatorは無限のデータストリームを表す素晴らしいツールです.無限データストリームはメモリに保存できません.generatorはitemを生成するたびに無限データストリームを表すことができます.
    次のコードはすべての奇数を生成することができます
    def all_even():
        n = 0
        while True:
            yield n
            n += 2

    4.generatorパイプライン(pipeline)
    generatorは、一連の操作に対して流水線操作を実行することができる.
    ファーストフードチェーンのログがあるとします.ログの4列目は1時間に販売されるピザの数で、5年近くのこのデータを合計したいと思っています.
    すべてのデータが文字であると仮定し、使用できないデータは「N/A」で表され、generatorを使用して実現できます.
    with open('sells.log') as file:
        pizza_col = (line[3] for line in file)
        per_hour = (int(x) for x in pizza_col if x != 'N/A')
        print("Total pizzas sold = ",sum(per_hour))
    この流水線は効率的で読みやすく、かっこいいように見えます!:)