Pythonの基本的な知識の組み合わせ-イテレータとジェネレータ

前書き

for i in [1,2,3,4,5,6]:
  print(i)

これは非常に単純なfor inステートメントです。反復にはどのような種類のオブジェクトを使用できますか?

コンテナ、反復可能オブジェクト、イテレータ

Pythonでは、すべてがオブジェクトであり、オブジェクトの抽象化がクラスであり、オブジェクトのコレクションがコンテナーです。

リストlist:[0,1,2]、タプルtuple:(0,1,2)、辞書dict:{0:0,1:1,2:2}、およびコレクションset:set{0,1,2}はすべてコンテナーです。コンテナは、さまざまな要素を組み合わせたものとして想像できます。異なるコンテナの違いは、内部データ構造の実装方法にあります。次に、さまざまなシナリオに応じて、時間計算量と空間計算量が異なる適切なコンテナを選択できます。

イテレータはnextメソッドを提供します。このメソッドを呼び出すと、コンテナ内の次のオブジェクトを取得したり、エラーメッセージを表示したりできますStopiteration。辞書やコレクションなどのコンテナは順序付けられていないため、リストのように要素のインデックスを指定する必要はありません。 、インデックスはありません。以前のブログ投稿<Pythonの基本的な知識の組み合わせ-辞書とコレクション>で述べたように、辞書の基礎となる実装が採用されてい哈希表ます。次の関数がコンテナ内のすべての要素を1つずつ取得できることを知っておく必要があります。

反復可能なオブジェクトの場合、関数iter()を介してイテレータを返し、next()関数をトラバースすることfor inもそのようなプロセスですが、非表示になっています。

isinstance(obj,Iterable)オブジェクトが反復可能かどうかは、Pythonの組み込みを介して判断できます。

def is_iterable(func):
    try:
        iter(func)
        return '可以'
    except Exception:
        return '不可以'

func = [
    1234,
    '1234',
    [1,2,3,4],
    (1,2,3,4),
    {
    
    1:1,2:2,3:3,4:4},
    {
    
    1,2,3,4},]
for fun in func:
    print('{} 是否可迭代\t\t {}'.format(fun,is_iterable(fun)))
# 输出
1234 是否可迭代		 不可以
1234 是否可迭代		 可以
[1, 2, 3, 4] 是否可迭代		 可以
(1, 2, 3, 4) 是否可迭代		 可以
{
    
    1: 1, 2: 2, 3: 3, 4: 4} 是否可迭代		 可以
{
    
    1, 2, 3, 4} 是否可迭代		 可以

この例を通して、番号1234を除くすべてのオブジェクトが反復可能であることがわかります。

ビルダー

ジェネレーターはシンプルで軽量なイテレーターです

たとえば、このようなイテレータ[i for i in range(1000000)]は100万個の数値を含むリストを生成できますが、各要素は生成後にメモリに格納されますが、メモリが不足するとOOM(OutOfMemeryError)エラーが発生します。

一度に大量のデータを生成するためにイテレーターが必要ない場合、ジェネレーターの役割が明らかになります。next()関数を呼び出している場合にのみ、次の変数が生成されます。Pythonで(i for i in range(10000000))ジェネレーターを初期化するために使用されます。

ジェネレーターはイテレーターのように多くのメモリーを消費せず、使用時にのみ呼び出されます。ジェネレーターは初期化時に生成された操作を実行する必要がないため、メモリー使用量はそれよりもはるかに少なくなります。イテレータ。

カスタムジェネレーター

例1:恒等式

数学にはアイデンティティがあります(1+2+3+···+n)^2 = 1^3 + 2^3 + 3^3+···+n^3。今では、ジェネレータを使用してこの式を実装しています。

def generator(num):
    i = 1
    while True:
        yield i ** num
        i += 1

gen_1 = generator(1)
gen_3 = generator(3)

def get_sum(n):
    sum1 , sum2 = 0, 0
    for i in range(n):
        num_n = next(gen_1)
        result = next(gen_3)
        print('num_n = {},result = {}'.format(num_n,result))
        sum1 += num_n
        sum2 += result

get_sum(10)
# 输出
num_n = 1,result = 1
num_n = 2,result = 8
num_n = 3,result = 27
num_n = 4,result = 64
num_n = 5,result = 125
num_n = 6,result = 216
num_n = 7,result = 343
num_n = 8,result = 512
num_n = 9,result = 729
num_n = 10,result = 1000

このコードのgenerator()関数のyieldキーワードは、実際には魔法のメソッドです。この関数の戻り値は、実際にはジェネレーターです。関数がこの部分まで実行されると、一時停止して飛び出します。next(gen_)関数が呼び出されると、関数はyield後で実行続けると、ローカル変数の値は1のままではなく、1に基づいて1ずつ増加し、その結果から、num_n1から10、およびresult1から1000に変化します。

例2:リスト内の番号の位置を見つける

リストを指定して、リスト内でこの番号の位置を見つけます

従来の方法
L = [1, 6, 2, 4, 5, 2, 3, 4, 6, 3, 3]
def find_element(list,target):
    result = []
    for location, num in enumerate(list):
        if num == target:
            result.append(location)
    return result

result = find_element(L,3)
print(result)
# 输出
[6, 9, 10]

従来の方法では、ライブラリ関数を使用して関数enumerate()のインデックスを返し、リストに追加します

イテレータ
L = [1, 6, 2, 4, 5, 2, 3, 4, 6, 3, 3]
def find_element_use_generator(list,target):
    for location, num in enumerate(list):
        if num == target:
            yield location

result = find_element_use_generator(L,3)
print(list(result))
# 输出
[6, 9, 10]

違いは明らかですがyield、戻り値は、出力する前にリストに変換するGenerator必要があるオブジェクトですlist

例3:サブシーケンスを決定する

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

この問題を解決するには:従来のアルゴリズムは欲張りアルゴリズムであり、リストの先頭から開始して2番目のシーケンスをスキャンする2つのポインターを維持します。最初の番号が最初のポインターと同じである場合、最初のポインターさらに、最初のポインタが最初のシーケンスの最後の要素をオーバーフローしたTrue場合は、を返しFalseます。それ以外の場合はを返します。

次に、イテレーターとジェネレーターを使用してこのアルゴリズムを完成させます。

def is_subsequence(list_a,list_b):
    list_b = iter(list_b)
    return all(i in list_b for i in list_a)

print(is_subsequence([1,2,4],[1,2,3,4,5]))
print(is_subsequence([1,4,3],[1,2,3,4,5]))
# 输出
True
False

アイデア:

  1. iter(list_b)目的は、list_b負荷をイテレータに変更することです
  2. for i in list_aオブジェクトをトラバースするために使用されるジェネレーターですa
  3. オブジェクトを移動する際a場合iも本b与えられたとき、iまた、中に存在する場合b

非常に巧妙に、ジェネレーターの特性を使用しnext()て、次のように、関数の実行時に現在のポインターが保存されます。

b = (i for i in range(5))

print(2 in b)
print(4 in b)
print(3 in b)
# 输出
True
True
False

明らかに、保存されたポインタの場合、print(3 in b)明らかにFalsea。

総括する

xxx

この記事で言及されている4つの異なる構造があります:①コンテナ、②反復可能オブジェクト、③イテレータ、④ジェネレータ

  • コンテナはiter()イテレータオブジェクトです。イテレータ関数を使用してイテレータをnext()取得でき、イテレータは関数を介して次の要素取得してトラバーサルをサポートできます。
  • Generatorは特別なイテレーターであり、軽量でシンプルなバージョンのイテレーターです。Generatorは、コードを明確にし、メモリ使用量を削減し、プログラム構造を最適化し、プログラムの実行速度を向上させることができます。
  • ジェネレーターは、Python2バージョンでコルーチンを実装するための重要な方法です。Python3.5以降、新しいasyncawit構文糖衣構文が導入され、コルーチンのジェネレーターの実装は遅れています。






ブログ投稿のフォローアップ更新については、私の個人ブログをフォローしてください:Stardust Blog

おすすめ

転載: blog.csdn.net/u011130655/article/details/113018710