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