1. ジェネレーターが必要な理由
上記の学習を通じて、リストの生成式を知ることができ、直接リストを作成することができます。ただし、メモリの制約により、リストの容量には確実に制限があります。さらに、1,000 万個の要素を含むリストを作成すると、多くの記憶域が必要になるだけでなく、最初の数個の要素にアクセスするだけで済む場合、後続の要素のほとんどが占有するスペースが無駄になります。
では、リストの要素を特定のアルゴリズムに従って計算できれば、ループ中に後続の要素を継続的に計算できるでしょうか?これにより、完全なリストを作成する必要がなくなり、スペースが大幅に節約されます。 Python では、ループと計算を同時に行うこの仕組みをジェネレーター: ジェネレーターと呼びます。
Python では、yield を使用する関数はジェネレーターと呼ばれます。
ジェネレータは通常の関数とは異なり、反復子を返す関数であり、反復演算のみに使用できます。
ジェネレーターを呼び出して実行するプロセスで、yield が発生するたびに、関数は一時停止して現在の実行情報をすべて保存し、yield の値を返します。そして、次回 next() メソッドが実行されるときに、現在の位置から実行を継続します。
それでは、ジェネレーターを作成するにはどうすればよいでしょうか?
2. ジェネレーターの作成
最も簡単で簡単な方法は、リスト生成の [] を ()
# -*- coding: UTF-8 -*- gen= (x * x for x in range(10)) print(gen)
に変更することです。 出力結果:
<generator object <genexpr> at 0x0000000002734A40>
リストの作成とジェネレーターの作成の違いは、最も外側の [] と () だけです。ただし、ジェネレーターは実際には数値のリストを作成するのではなく、計算されるたびに項目を「生成」するジェネレーターを返します。ジェネレーター式は「遅延評価」 (「遅延評価」とも訳されます。必要に応じて呼び出すこの方法は遅延と訳したほうが良いと思います) を使用し、取得 (評価) 時にのみ割り当てられるため、メモリ効率が高くなります。リストは長いです。
ジェネレーターの作成方法はわかりましたが、内部の要素を表示するにはどうすればよいでしょうか?
3. ジェネレーターの要素を走査する
私たちの考え方によれば、走査には for ループが使用されます。
# -*- coding: UTF-8 -*- gen= (x * x for x in range(10)) for num in gen : print(num)そうです、このように直接トラバースできます。もちろん、イテレータについても上で説明しましたが、next() を使用してトラバースできるでしょうか?もちろん可能です。
4. ジェネレーターを関数の形式で実装する
前述したように、ジェネレーターを作成する最も簡単な方法は、[] のリストを生成することです。への変更 ()。なぜ突然関数の形で作成されるのでしょうか? 実際、ジェネレーターは反復子でもありますが、反復できるのは 1 回だけです。これは、すべての値をメモリに保存するのではなく、実行時に値を生成するためです。これらを使用するには、「for」ループを使用するか、反復可能な関数や構造体にそれらを渡して反復処理します。実際のアプリケーションでは、ほとんどのジェネレーターは関数を通じて実装されます。では、関数を使用してどのように作成するのでしょうか?心配しないで、この例を見てみましょう:
# -*- coding: UTF-8 -*- def my_function(): for i in range(10): print ( i ) my_function()
出力結果:
0 1 2 3 4 5 6 7 8 9
これをジェネレーターに変える必要がある場合は、print を変更するだけです。 (i) i が得られれば十分です。修正された例を見てみましょう:
# -*- coding: UTF-8 -*- def my_function(): for i in range(10): yield i print(my_function())
出力結果:
<generator object my_function at 0x0000000002534A40>
ただし、この例はジェネレーターの使用には非常に不向きであり、ジェネレーターの利点として、ジェネレーターの最適なアプリケーションは、特に結果セットにループが含まれている場合、多数の計算結果セットを同時にメモリーに割り当てたくないことです。これは多くのリソースを消費するためです。
たとえば、次はフィボナッチ数列を計算するジェネレーターです:
# -*- coding: UTF-8 -*- def fibon(n): a = b = 1 for i in range(n): yield a a, b = b, a + b # 引用函数 for x in fibon(1000000): print(x , end = ' ')
実行の効果:
次のようなパラメーターを実行すると、この方法ではリソースをあまり使用しないため、スタック状態があるとは言えません。ここで最も理解しにくいのは、ジェネレーターと関数の実行フローが異なることです。関数は順番に実行され、return ステートメントまたは関数ステートメントの最後の行に到達すると戻ります。ジェネレーターとなる関数は next() が呼び出されるたびに実行され、yield ステートメントに遭遇すると戻り、再度実行されると最後に返された yield ステートメントから実行を継続します。
例:
# -*- coding: UTF-8 -*- def odd(): print ( 'step 1' ) yield ( 1 ) print ( 'step 2' ) yield ( 3 ) print ( 'step 3' ) yield ( 5 ) o = odd() print( next( o ) ) print( next( o ) ) print( next( o ) ) 输出的结果: step 1 1 step 2 3 step 3 5
ご覧のとおり、odd は通常の関数ではなく、ジェネレーターです。実行中に、yield に遭遇すると中断され、実行は次回に続きます。 yield を 3 回実行すると、それ以上実行する yield がなくなり、print(next(o)) の印刷を続けるとエラーが報告されます。したがって、エラーは通常、ジェネレーター関数でキャプチャされます。
5. Yang Hui Triangle の印刷
ジェネレーターを学習した後、ジェネレーターのナレッジ ポイントを直接使用して Yang Hui Triangle を印刷できます:
# -*- coding: UTF-8 -*- def triangles( n ): # 杨辉三角形 L = [1] while True: yield L L.append(0) L = [ L [ i -1 ] + L [ i ] for i in range (len(L))] n= 0 for t in triangles( 10 ): # 直接修改函数名即可运行 print(t) n = n + 1 if n == 10: break
出力結果は次のとおりです:
[1] [1, 1] [1, 2, 1] [1, 3, 3, 1] [1, 4, 6, 4, 1] [1, 5, 10, 10, 5, 1] [1, 6, 15, 20, 15, 6, 1] [1, 7, 21, 35, 35, 21, 7, 1] [1, 8, 28, 56, 70, 56, 28, 8, 1] [1, 9, 36, 84, 126, 126, 84, 36, 9, 1]次のセクション