ホームページ >バックエンド開発 >Python チュートリアル >Python ジェネレーター (Generator) についての深い理解
リスト生成を通じて簡単かつ直接リストを作成できますが、メモリの制約により、リストの容量は確実に制限されます。さらに、100 万個の要素を含むリストを作成すると、多くの記憶領域が必要になるだけでなく、最初の数要素にアクセスするだけで済む場合、後続の要素のほとんどが占有する領域が無駄になります。
では、リストの要素が特定のアルゴリズムに従って計算できれば、ループ中に後続の要素を継続的に計算できるでしょうか?これにより、完全なリストを作成する必要がなくなり、スペースが大幅に節約されます。 Python では、ループと計算を同時に行うこの仕組みをジェネレーターと呼びます。
ジェネレーターを作成するには、さまざまな方法があります。最初の方法は非常に簡単で、リスト生成式の [] を () に変更してジェネレーターを作成します。
>>> 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>
mylist と gen の作成の唯一の違いは、Mylist がリストであることです。 gen はジェネレーターです。
リストの各要素を直接出力できますが、ジェネレーターの各要素を出力するにはどうすればよいでしょうか?
それらを 1 つずつ出力したい場合は、ジェネレーターの next() メソッドを使用できます。
>>> 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
前述したように、ジェネレーターは next() が呼び出されるたびにアルゴリズムを保存します。最後の要素が最後の要素で、それ以上要素が存在しない場合は、StopIteration エラーがスローされます。
実際、 next() メソッドの代わりに for ループを使用できます。これは効率的なプログラミングのアイデアにより一致しています:
>>> gen = ( x for x in range(1, 10)) >>> for num in gen: ... print num ... 1 2 3 4 5 6 7 8 9
generator は非常に強力です。計算アルゴリズムが比較的複雑で、リスト生成のような for ループを使用して実装できない場合は、関数を使用して実装することもできます。
たとえば、有名なフィボナッチ数列では、最初と 2 番目の数値を除き、最初の 2 つの数値を加算することで任意の数値を得ることができます:
1, 1, 2, 3, 5 , 8, 13, 21, 34 , ...
フィボナッチ数列はリスト生成を使用して記述することはできませんが、関数を使用して簡単に出力できます:
def fib(max): n = 0 a, b = 0, 1 while n < max: print b a, b = b, a + b n = n + 1
上記の関数はフィボナッチを出力できますフィボナッチ数列の最初の N 個の数値:
>>> fib(6)
Ifよく見ると、fib 関数が実際にフィボナッチ数列の計算ルールを定義していることがわかります。このロジックは実際にはジェネレーターと非常によく似ています。
言い換えれば、上記の関数はジェネレーターまであと 1 ステップです。 fib 関数をジェネレーターに変えるには、print b を yield b に変更するだけです:
def fib(max): n = 0 a, b = 0, 1 while n < max: yield b a, b = b, a + b n = n + 1
これはジェネレーターを定義する別の方法です。関数定義に yield キーワードが含まれている場合、その関数はもはや通常の関数ではなくジェネレーターです:
>>> fib(6)
ここで、最も理解しにくいのは、ジェネレーターと関数の実行プロセスが異なることです。関数は順番に実行され、return ステートメントまたは関数ステートメントの最後の行に到達すると戻ります。ジェネレーターとなる関数は next() が呼び出されるたびに実行され、yield ステートメントに遭遇するとリターンし、再度実行されると最後に返された yield ステートメントから実行を継続します。
簡単な例として、数値 1、3、5 を順番に返すジェネレーターを定義します。
>>> def odd(): ... print 'step 1' ... yield 1 ... print 'step 2' ... yield 3 ... print 'step 3' ... 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
odd は通常の関数ではなく、実行中に発生すると割り込みを行うジェネレーターであることがわかります。次回は実行を続けます。 yield を 3 回実行すると、それ以上実行する yield がなくなるため、next() が 4 回目に呼び出されたときにエラーが報告されます。
fib の例に戻ると、ループ中に yield を呼び出し続けると、中断され続けます。もちろん、ループを終了するにはループの条件を設定する必要があります。そうしないと、無限の数がリストされます。
同様に、関数をジェネレーターに変更した後、基本的に next() を使用して呼び出すことはありませんが、for ループを直接使用して繰り返します。
>>> for n in fib(6): ... print n ...
ジェネレーターは、Python では非常に強力なツールであり、リストを変更するだけで済みます。ジェネレーターに生成することも、関数を使用して複雑なロジック ジェネレーターを実装することもできます。
ジェネレーターの動作原理を理解するために、ジェネレーターは for ループ中に次の要素を継続的に計算し、適切な条件下で for ループを終了します。関数から変更されたジェネレーターの場合、return ステートメントに遭遇するか、関数本体の最後の行が実行されると、それがジェネレーターを終了する命令となり、それに応じて for ループが終了します。