テスト開発のためのPythonコアノート(15):イテレーターとジェネレーター

15.1反復可能反復可能

forループを覚えていますか?forループは、文字列と、リスト、タプル、辞書、セットなどのコンテナータイプを反復処理できますが、これらのデータ型をforで反復処理できる理由を知っていますか?これらのオブジェクトは反復可能なオブジェクトであるためです。

それが反復可能オブジェクトであるかどうかを判断するためにisinstance(obj, Iterable)、出力Trueはobjオブジェクトが反復可能(反復可能)であることを示していると判断できます。

15.2イテレータイテレータ

イテレータを使用すると、プログラマーは非順次型、つまりリスト、要素、辞書、およびセット以外の型を反復処理できます。

イテレータは、next()メソッドを介してオブジェクト内の次の要素を取得します。これはデータストリームと見なすことができます。その長さはわかりません。次のデータを使用しながら計算するのは怠惰な計算です。イテレータオブジェクトは、コレクションの最初の要素から、すべての要素にアクセスするまでアクセスされます。

イテレータの主な利点は、インデックス値に関係なく、メモリを節約できることです(ループプロセス中に、データを一度に読み取る必要がありません。これは、ファイルもイテレータオブジェクトであるため、ファイルオブジェクトを処理する場合に特に便利です)。怠惰な計算(値は必要に応じて取得されます)計算);

with open('java.txt') as f:
    for line in f:
        print(line)

このようにして、ファイル全体を一度に読み取るのではなく、一度に1行ずつ読み取って処理し、メモリを節約します。

イテレータの欠点は、トラバースできるのは1回だけで、2回目はトラバースできないことです。アクセスをトラバースするときは、データに1つずつしかアクセスできないため、データがなくなるまで戻ることはできません。つまり、イテレータは前進することしかできず、後退することはできません。

l = ['a', 'n', 's', 'd']
l_iterator = iter(l)  # 通过iter函数得到一个迭代器
for i in l_iterator:
    print(i)

for i in l_iterator:  # 经过第一遍迭代后,迭代器中已经没有数据了,或者说已经访问到尾部了。
    print(i)

15.2.1 iter()関数を介したイテレータの取得

tuple、list、dict、およびstrは反復可能ですが、反復子ではありません。iter()関数によってイテレータを返すことができます。トラバーサルはnext()メソッドを介して実行でき、次のメソッドが呼び出されて、コンテナーの次のオブジェクトを取得するか、StopIterationエラーを取得します。Pythonのforループ反復タプルとリストは、基本的に最初にそれらをイテレーターに変換し、次にnext()関数を継続的に呼び出すことによってそれらを実装します。

l_iterator=iter([1,2,3,4,5])

15.2.1クラスを介したイテレータの実装

Pythonのクラスによって実装される多くのイテレータ(reversed()やenumerate()など)があります。ソースコードを見ると、これらのクラスが__next__メソッドと__iter__メソッド実装していることがわかります。

クラスオブジェクトを反復可能にしたい場合、クラスをイテレータとして実装するには、クラスをIterableから継承させてから、__next__ methodと__iter__methodの2つのメソッドをオーバーライドします。

__iter__このメソッドは、特別なイテレータオブジェクトを返します。

__next__次のイテレータオブジェクトを返します。

たとえば、正の整数が0に達するまで1ずつデクリメントするデクリメントイテレータを実装します。

from collections.abc import Iterable


class Decrease(Iterable):
    def __init__(self, init):
        self.init = init

    def __iter__(self):  # 返回对象本身
        return self

    def __next__(self):
        while 0 < self.init:
            self.init -= 1
            return self.init # 返回下一个
        raise StopIteration  #  通过 raise 终断next


for i in Decrease(6):  # 可以用for循环迭代这个类对象了
    print(i)

15.3itertoolsモジュール

このモジュールは、一連の高速で効率的なイテレータを実装します。これらのイテレータは、単独で、または組み合わせて使用​​すると便利です。このセクションでは、いくつかの非常に便利なイテレータを紹介します。

組み込みのイテレータを確認するためのヘルプ(itertools)。Pycharmエディターを使用する場合は、Shiftキーを2回押して、itertoolsと入力し、[プロジェクト以外のアイテムを含める]をオンにして、ポップアップページでitertoolsファイルを選択してitertools.pyファイルを入力します。itertoolsモジュールによって提供されるすべてのイテレータを一覧表示する次のようなものが表示されます。

"""
Functional tools for creating and using iterators.

Infinite iterators:
count(start=0, step=1) --> start, start+step, start+2*step, ...
cycle(p) --> p0, p1, ... plast, p0, p1, ...
repeat(elem [,n]) --> elem, elem, elem, ... endlessly or up to n times

Iterators terminating on the shortest input sequence:
accumulate(p[, func]) --> p0, p0+p1, p0+p1+p2
chain(p, q, ...) --> p0, p1, ... plast, q0, q1, ... 
chain.from_iterable([p, q, ...]) --> p0, p1, ... plast, q0, q1, ... 
compress(data, selectors) --> (d[0] if s[0]), (d[1] if s[1]), ...
dropwhile(pred, seq) --> seq[n], seq[n+1], starting when pred fails
groupby(iterable[, keyfunc]) --> sub-iterators grouped by value of keyfunc(v)
filterfalse(pred, seq) --> elements of seq where pred(elem) is False
islice(seq, [start,] stop [, step]) --> elements from
       seq[start:stop:step]
starmap(fun, seq) --> fun(*seq[0]), fun(*seq[1]), ...
tee(it, n=2) --> (it1, it2 , ... itn) splits one iterator into n
takewhile(pred, seq) --> seq[0], seq[1], until pred fails
zip_longest(p, q, ...) --> (p[0], q[0]), (p[1], q[1]), ... 

Combinatoric generators:
product(p, q, ... [repeat=1]) --> cartesian product
permutations(p[, r])
combinations(p, r)
combinations_with_replacement(p, r)
"""

以下では、これらのイテレータのいくつかを学習することに焦点を当てます。

15.3.1イテレータチェーンの連結

複数のイテレータを単一のイテレータに変換します。Pycharmのナビゲーションバーで、[ナビゲート]-> [ファイル構造]を選択して、itertools.pyモジュール内のすべてのクラスを一覧表示します。

ここに画像の説明を挿入

チェーンクラスをクリックして、チェーンの説明を表示します。

class chain(object):
    """
    chain(*iterables) --> chain object
    
    Return a chain object whose .__next__() method returns elements from the
    first iterable until it is exhausted, then elements from the next
    iterable, until all of the iterables are exhausted.
    """

ドキュメントによると、最初に最初のイテレータのすべての要素を返し、次にイテレータのすべての要素が使い果たされるまで、次のイテレータのすべての要素を返すイテレータを作成します。複数のイテレータを単一のイテレータに変換します。いくつか例を挙げると、Pycharmで編集します。

from itertools import chain

chain_iterator = chain("ABCDef", "1234")  # 两个可迭代对象:"ABCDef"和"1234"
print(list(chain_iterator))  # ['A', 'B', 'C', 'D', 'e', 'f', '1', '2', '3', '4']

chain()の一般的な使用法は、異なるコレクション内のすべての要素に対して何らかの操作を実行する場合です。たとえば、両方のリストの要素の2乗を合計して、新しいリストを作成するとします。これは次のように実行できます。

from itertools import chain


def square_multi_iterables(*iterables):  # 定义一个生成器
    chain_iterator = chain(*iterables)  # 利用chain生成迭代器
    for l in chain_iterator:  # 对迭代器里面的元素求进行平方
        yield l * l  # yield 返回


if __name__ == '__main__':
    lst1 = [1, 2, 3]
    lst2 = [4, 5, 6]
    for i in square_multi_iterables(lst1, lst2):
        print(i)

    print(list(square_multi_iterables(lst2, lst1)))

このソリューションは、2つの別々のループを使用するよりもはるかに洗練されています。

15.3.2累積イテレータ累積

まず、ソースコードの内容を見てください。

class accumulate(object):
    """
    accumulate(iterable[, func]) --> accumulate object
    
    Return series of accumulated sums (or other binary function results).
    """

func関数にイテレータを適用し、イテレータの累積イテレータを返します。funcが指定されていない場合、反復可能なオブジェクトの累積合計で構成される反復子が返されます。

説明する例を見てください。

from itertools import accumulate

lst = [1, 2, 3, 4, 5, 6]
for i in accumulate(lst):
    print(i)
print(list(accumulate(lst)))  # 返回 [1, 3, 6, 10, 15, 21]

この例ではfunc関数は提供されておらず、デフォルトでは元のシーケンスを累積して合計します。func関数を提供する使用法を見てみましょう。

from itertools import accumulate

lst = [1, 2, 3, 4, 5, 6]
for i in accumulate(lst, lambda x, y: x * y):
    print(i)
print(list(accumulate(lst, lambda x, y: x * y)))  # 返回 [1, 2, 6, 24, 120, 720]

15.3.3順列および組み合わせイテレータ

それでもコードが言っていることを見てください:

class combinations(object):
    """
    combinations(iterable, r) --> combinations object
    
    Return successive r-length combinations of elements in the iterable.
    
    combinations(range(4), 3) --> (0,1,2), (0,1,3), (0,2,3), (1,2,3)
    """

iterable入力iterableの要素で構成される長さrのサブシーケンスを返します。組み合わせは辞書式順序で返されます。したがって、入力反復可能が順序付けられている場合、結果の結合されたタプルも順序付けられます。

要素の値が同じであっても、位置が異なる要素は異なると見なされます。要素が個別に異なる場合、各組み合わせに重複する要素はありません。

たとえば、「ABCD」に従って['AB'、'AC'、'AD'、'BC'、'BD'、'CD']を出力する方法

def my_combinations(iterables, length):
    for i in combinations(iterables, length):  
        yield "".join(i)


if __name__ == '__main__':
    lst=[]
    for element in my_combinations("ABCD", 2):  # 两个元素组成的排列组合
        lst.append(element)

    print(lst)  # ['AB', 'AC', 'AD', 'BC', 'BD', 'CD']

15.3.3圧縮イテレータ圧縮

ソースコードの内容をご覧ください。

class compress(object):
    """
    compress(data, selectors) --> iterator over selected data
    
    Return data elements corresponding to true selector elements.
    Forms a shorter iterator from selected data elements using the
    selectors to choose the data elements.
    """

セレクターが真理テストされたデータ内の要素を返すイテレーターを作成します。イテレータは、2つの短い方の長さで停止します。True

print(list(compress('ABCDEF', [1, 0, 1, 0, 1, 1])))  # 输出['A', 'C', 'E', 'F']

次のコードに相当します。

def compress(data, selectors):
    return (d for d, s in zip(data, selectors) if s)

15.3.4ドロップイテレータdropwhile

ソースコードの説明を見てください。

class dropwhile(object):
    """
    dropwhile(predicate, iterable) --> dropwhile object
    
    Drop items from the iterable while predicate(item) is true.
    Afterwards, return every element until the iterable is exhausted.
    """

述語がtrueの場合にこれらの要素を破棄し、他の要素を返すイテレータを作成します。イテレータは、述部が初めてfalseになるまで出力を生成しないため、起動に一定の時間がかかる場合があります。

例えば:

from itertools import dropwhile

print(list(dropwhile(lambda x: x < 5, [1, 4, 6, 4, 1])))  # 输出 [6, 4, 1]

ほぼ同等:

def my_dropwhile(predicate, iterable):
    iterable = iter(iterable)
    for x in iterable:
        if not predicate(x):
            yield x  # 返回第一个不满足predicate的值后退出这个循环
            break
    for x in iterable:  # 接着循环剩下的元素
        yield x


print(list(my_dropwhile(lambda x: x < 5, [1, 4, 6, 4, 1])))  # 输出 [6, 4, 1]

その他のイテレータについては、itertools.pyソースコードを参照してください。

15.3ジェネレーター(特別なイテレーター)

イールドを持つ関数はジェネレーターであり、ジェネレーターはイテレーターでもあります。したがって、ジェネレーターには、上記のイテレーターの特性もあります。

イールドの一般的な理解は、関数の戻り値キーワードと組み合わせることができます。イールドは特別な種類のリターンです。リターンと同様に、yieldに遭遇するとすぐに実行が戻るため、特別なリターンと呼ばれます。

違いは、次に関数を入力するときに、yieldの次のステートメントに直接移動し、関数に戻った後も、関数本体のコードの最初の行から実行を開始することです。

イテレータの宣言は簡単です。リスト内包表記式[ifori in range(100000000)]は、1億個の要素のリストを生成できます。各要素は生成後にメモリに保存され(このプロセスは非常に遅い)、大量のメモリを消費し、十分なメモリがない場合はOOMエラーが発生します。ただし、要素の累積や合計など、事前に多くの要素をメモリに保存する必要がない場合もあります。各要素が追加された時点でどれだけあるかを知るだけで、次の場合にそれを破棄できます。不足します。

ジェネレータジェネレータは、すべての値を事前に生成してメモリに配置するわけではありません。次のメソッド(ジェネレータメソッド)が呼び出された場合にのみ、次の変数が生成されるため、コードの実行が高速化され、メモリが節約されます

ジェネレータを取得するには、次の2つの方法があります。

15.3.1括弧定義ジェネレータ

[]を()に置き換えると、リスト内包表記をジェネレーターに変更するだけで、ジェネレーター(i for i in range(100000000))を取得できます。リスト内包は最も一般的に使用されますが、リスト内包をジェネレーターに変えてみてください。これはメモリを節約するためです。メモリを節約するという考えは、レベルを反映するために、あらゆる場所のコードに反映される必要があります。

15.3.2yieldキーワードを使用したジェネレーターの定義

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1
    return 'done'

next()が呼び出されるたびに実行され、yieldステートメントが一時停止され、yield式の後の値が返され、nextが再度呼び出されるとyieldステートメントから実行が続行されます。

15.3.3ジェネレーターの価値

ジェネレータは、遅延計算を利用してメモリを節約するだけでなく、コードの可読性を向上させることもできます。たとえば、テキスト内の各単語の開始添え字を見つけます。ジェネレーターなしのソリューション:

def index_words(text):
    result = []  # 存放下标
    if text:
        result.append(0)
    for index, letter in enumerate(text, 1):  # 第二个参数是1,如果不写是什么情况
        if letter == " ":
            result.append(index)
    return result


if __name__ == '__main__':
    enumerate_desc = "The enumerate object yields pairs containing a count (from start, which defaults to zero) and a value yielded by the iterable argument."
    print(index_words(enumerate_desc))

ジェネレーターを使用する場合:

def index_words(text):
    if text:
        yield 0  # 第一次返回
    for index, letter in enumerate(text, 1): 
        if letter == " ":
            yield index  # 每次调用返回


if __name__ == '__main__':
    enumerate_desc = "The enumerate object yields pairs containing a count (from start, which defaults to zero) and a value yielded by the iterable argument."
    for index in index_words(enumerate_desc):
        print(index)
    print(list(index_words(enumerate_desc)))

ジェネレーターを使用できるコードはよりクリーンで、コード行が少なくなっています。ジェネレータを使用しない場合、結果ごとに、最初に追加操作が実行され、最後に結果が返されます。ジェネレーターを使用する場合、直接イールドインデックスは、リスト操作によって引き起こされる干渉を減らします。コードがインデックスを返すことであることが一目でわかります。

Kafkaの消費データは、歩留まりジェネレーターを使用できます。pytestのフィクスチャ機能でも使用され、yieldはwebdrvierの起動にも使用されます。

作業では必ずジェネレーターを頻繁に使用してください。ただし、ジェネレーターはイテレーターでもあるため、ジェネレーターは1回しかトラバースできないことに注意してください。

15.4練習用の質問

  1. リスト内の要素のすべての添え字インデックスのリストを検索します
def index_generator(L, target):
    for i, num in enumerate(L):
        if num == target:
            yield i

print(list(index_generator([1, 6, 2, 4, 5, 2, 8, 6, 3, 2], 2)))

########## 输出 ##########

[2, 5, 9]

index_generatorはGeneratorオブジェクトを返します。これは、printで出力する前に、listを使用してリストに変換する必要があります。

  1. 2つのシーケンスが与えられた場合、最初のシーケンスが2番目のサブシーケンスであるかどうかを判別します。

LeetCodeリンクはここにあります:https://leetcode.com/problems/is-subsequence/。シーケンスはリストであり、サブシーケンスとは、1つのリストの要素が2番目のリストに順番に表示されることを意味しますが、必ずしも互いに隣接している必要はありません。たとえば、[1、3、5]は[1、2、3、4、5]のサブシーケンスですが、[1、4、3]はそうではありません。

この問題を解決するために、従来のアルゴリズムは欲張りアルゴリズムです。2つのリストの先頭を指す2つのポインターを維持し、2番目のシーケンスを最後までスイープします。番号が最初のポインターと同じである場合は、最初のポインターを1ステップ進めます。最初のポインタが最初のシーケンスの最後の要素から移動した場合はTrueを返し、それ以外の場合はFalseを返します。

ただし、このセクションでは、これを実現するためにジェネレーターを使用します。

def is_subsequence(a, b):
    b = iter(b)
    return all(i in b for i in a)

print(is_subsequence([1, 3, 5], [1, 2, 3, 4, 5]))
print(is_subsequence([1, 4, 3], [1, 2, 3, 4, 5]))

########## 输出 ##########

True
False

おすすめ

転載: blog.csdn.net/liuchunming033/article/details/107896326