ホームページ > バックエンド開発 > Python チュートリアル > Python ジェネレーター (Generator) についての深い理解

Python ジェネレーター (Generator) についての深い理解

高洛峰
リリース: 2016-10-17 16:22:23
オリジナル
968 人が閲覧しました

リスト生成を通じて簡単かつ直接リストを作成できますが、メモリの制約により、リストの容量は確実に制限されます。さらに、100 万個の要素を含むリストを作成すると、多くの記憶領域が必要になるだけでなく、最初の数要素にアクセスするだけで済む場合、後続の要素のほとんどが占有する領域が無駄になります。

では、リストの要素が特定のアルゴリズムに従って計算できれば、ループ中に後続の要素を継続的に計算できるでしょうか?これにより、完全なリストを作成する必要がなくなり、スペースが大幅に節約されます。 Python では、ループと計算を同時に行うこの仕組みをジェネレーターと呼びます。


ジェネレーターを作成するには、さまざまな方法があります。最初の方法は非常に簡単で、リスト生成式の [] を () に変更してジェネレーターを作成するだけです。はリスト、gen はジェネレーターです。

リストの各要素を直接出力できますが、ジェネレーターの各要素を出力するにはどうすればよいでしょうか?

それらを 1 つずつ出力したい場合は、ジェネレーターの next() メソッドを使用できます:

>>> mylist = [ x for x in range(1, 10)]
>>> mylist
[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> gen = (x for x in range(1,10))
>>> gen
<generator object <genexpr> at 0x7f1d7fd0f5a0>
ログイン後にコピー

ジェネレーターは next() が呼び出されるたびにアルゴリズムを保存すると言いました。次の要素の値が計算され、最後の要素が計算されて要素がなくなるまで、StopIteration エラーがスローされます。

実際、 next() メソッドの代わりに for ループを使用できます。これは効率的なプログラミングのアイデアにより一致しています:

>>> gen.next()
1
>>> gen.next()
2
>>> gen.next()
3
...
>>> gen.next()
9
>>> gen.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
ログイン後にコピー


ジェネレーターは非常に強力です。計算アルゴリズムが比較的複雑で、リスト生成のような for ループを使用して実装できない場合は、関数を使用して実装することもできます。


たとえば、有名なフィボナッチ数列では、最初と 2 番目の数を除いて、最初の 2 つの数を加算することで任意の数を得ることができます:

>>> gen = ( x for x in range(1, 10))
>>> for num in gen:
...     print num
... 
1
2
3
4
5
6
7
8
9
ログイン後にコピー


Fi ボラッチ数列はリストを使用して書くことはできません生成されますが、関数を使用して簡単に出力できます:

1, 1, 2, 3, 5, 8, 13, 21, 34, ...
ログイン後にコピー

上記の関数は、フィボナッチ数列の最初の N 個の数値を出力できます:

def fib(max):
    n = 0 
    a, b = 0, 1
    while n < max:
        print b
        a, b = b, a + b
        n = n + 1
ログイン後にコピー

よく見ると、次のことがわかります。 fib 関数は実際にはフィボナッチ数列の計算ルールを定義しており、最初の要素から開始して後続の要素を計算できます。このロジックは実際にはジェネレーターと非常によく似ています。

つまり、上記の関数はジェネレーターまであと 1 ステップです。 fib 関数をジェネレーターに変えるには、print b を yield b に変更するだけです。

>>> fib(6)
1
1
2
3
5
8
ログイン後にコピー


これは、ジェネレーターを定義する別の方法です。関数定義に yield キーワードが含まれている場合、その関数はもはや通常の関数ではなく、ジェネレーターです:

def fib(max):
    n = 0 
    a, b = 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1
ログイン後にコピー

ここで、最も理解しにくいのは、ジェネレーターと関数の実行フローが異なることです。関数は順番に実行され、return ステートメントまたは関数ステートメントの最後の行に到達すると戻ります。ジェネレーターとなる関数は next() が呼び出されるたびに実行され、yield ステートメントに遭遇するとリターンし、再度実行されると最後に返された yield ステートメントから実行を継続します。

簡単な例として、数値 1、3、5 を順番に返すジェネレーターを定義します。

>>> fib(6)
<generator object fib at 0x104feaaa0>
ログイン後にコピー


実行中、odd は通常の関数ではなく、ジェネレーターであることがわかります。に遭遇すると、yield に達すると中断され、次回実行が続行されます。 yield を 3 回実行すると、それ以上実行する yield がなくなるため、next() が 4 回目に呼び出されたときにエラーが報告されます。

fib の例に戻ると、ループ中に yield を呼び出し続けると、中断され続けます。もちろん、ループを終了するにはループの条件を設定する必要があります。そうしないと、無限の数がリストされます。


同様に、関数をジェネレーターに変更した後、基本的に next() を使用して関数を呼び出すことはありませんが、for ループを直接使用して繰り返します。

>>> def odd():
...     print &#39;step 1&#39;
...     yield 1
...     print &#39;step 2&#39;
...     yield 3
...     print &#39;step 3&#39;
...     yield 5
...
>>> o = odd()
>>> o.next()
step 1
1
>>> o.next()
step 2
3
>>> o.next()
step 3
5
>>> o.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
ログイン後にコピー


generator は非常に強力なツールであり、Python では、単純にリスト生成をジェネレーターに変更することも、関数を使用して複雑なロジック ジェネレーターを実装することもできます。

ジェネレーターの動作原理を理解するために、ジェネレーターは for ループ中に次の要素を継続的に計算し、適切な条件下で for ループを終了します。関数から変更されたジェネレーターの場合、return ステートメントに遭遇するか、関数本体の最後の行が実行されると、それがジェネレーターを終了する命令となり、それに応じて for ループが終了します。

ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート