(六)ジェネレータ


1.ジェネレータとは
リスト生成式により、リストを直接作成できます.ただし、メモリの制限を受けると、リストの容量は限られているに違いありません.また、100万個の要素を含むリストを作成すると、大きなストレージスペースを消費するだけでなく、前のいくつかの要素にアクセスするだけで、後ろのほとんどの要素が消費するスペースが無駄になります.だから、リスト要素が何らかのアルゴリズムで推定できるなら、ループの過程で後続の要素を絶えず推定することができますか?これにより、listを完全に作成する必要がなくなり、大量のスペースを節約できます.Pythonでは,このように循環しながら計算するメカニズムをジェネレータ:generatorと呼ぶ.  
2、ジェネレータの作成方法(一)
ジェネレータを作成するには、さまざまな方法があります.1つ目の方法は,リスト生成式の[]を()に変更するだけで簡単である.
In [15]: L = [ x*2 for x in range(5)]

In [16]: L
Out[16]: [0, 2, 4, 6, 8]

In [17]: G = ( x*2 for x in range(5))

In [18]: G
Out[18]:  at 0x7f626c132db0>

In [19]:

LとGを作成する違いは、最外層の[]と()のみであり、Lはリストであり、Gはジェネレータである.Lの各要素を直接印刷することができますが、Gの各要素をどのように印刷しますか?1つずつ印刷する場合は、next()関数を使用してジェネレータの次の戻り値を取得します.
In [19]: next(G)
Out[19]: 0

In [20]: next(G)
Out[20]: 2

In [21]: next(G)
Out[21]: 4

In [22]: next(G)
Out[22]: 6

In [23]: next(G)
Out[23]: 8

In [24]: next(G)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
24-380e167d6934> in ()
----> 1 next(G)

StopIteration: 

In [25]:
In [26]: G = ( x*2 for x in range(5))

In [27]: for x in G:
   ....:     print(x)
   ....:     
0
2
4
6
8

In [28]:

ジェネレータは、next(G)を呼び出すたびにGの次の要素の値を計算し、最後の要素まで計算し、より多くの要素がない場合にStopIterationの異常を放出するアルゴリズムを保存します.もちろん、next()を呼び出し続ける方法は望ましくなく、ジェネレータも反復可能なオブジェクトであるため、forループを使用するのが正しい.したがって、ジェネレータを作成すると、next()はほとんど呼び出されず、forループで反復され、StopIteration異常に関心を持つ必要はありません.
3.ジェネレータの作成方法(二)
generatorはとても強いです.推定アルゴリズムが複雑で,リスト生成式のようなforループでは実現できない場合,関数で実現することも可能である.例えば、有名なフィボナッチ数列(Fibonacci)は、1番目と2番目の数を除いて、いずれの数も前の2つの数から加算することができます:1、1、2、3、5、8、13、21、34、...
フィポラチ数列はリスト生成式では書けませんが、関数で印刷するのは簡単です.
In [28]: def fib(times):
   ....:     n = 0
   ....:     a,b = 0,1
   ....:     while n1
   ....:     return 'done'
   ....: 

In [29]: fib(5)
1
1
2
3
5
Out[29]: 'done'

fib関数は実際にフィボラッチ数列を定義した推定規則であり,最初の要素から後続の任意の要素を推定することができ,この論理はgeneratorに非常に類似していることがわかる.print(b)をyield bに変更すればいいのです
In [30]: def fib(times):
   ....:     n = 0
   ....:     a,b = 0,1
   ....:     while n1
   ....:     return 'done'
   ....: 

In [31]: F = fib(5)

In [32]: next(F)
Out[32]: 1

In [33]: next(F)
Out[33]: 1

In [34]: next(F)
Out[34]: 2

In [35]: next(F)
Out[35]: 3

In [36]: next(F)
Out[36]: 5

In [37]: next(F)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
37-8c2b02b4361a> in ()
----> 1 next(F)

StopIteration: done

4. send
yieldに実行するとgen関数の役割が一時的に保存され、iの値が返されます.tempは次のc.send(「python」)すなわちsendから送られてきた値を受信し,c.next()はc.send(None)に等価である.
In [10]: def gen():
   ....:     i = 0
   ....:     while i<5:
   ....:         temp = yield i
   ....:         print(temp)
   ....:         i+=1
   ....:

next関数を使用してアクセス
In [11]: f = gen()

In [12]: next(f)
Out[12]: 0

In [13]: next(f)
None
Out[13]: 1

In [14]: next(f)
None
Out[14]: 2

In [15]: next(f)
None
Out[15]: 3

In [16]: next(f)
None
Out[16]: 4

In [17]: next(f)
None
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
17-468f0afdf1b9> in ()
----> 1 next(f)

StopIteration:

使用__next__()メソッドアクセス
In [18]: f = gen()

In [19]: f.__next__()
Out[19]: 0

In [20]: f.__next__()
None
Out[20]: 1

In [21]: f.__next__()
None
Out[21]: 2

In [22]: f.__next__()
None
Out[22]: 3

In [23]: f.__next__()
None
Out[23]: 4

In [24]: f.__next__()
None
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
24-39ec527346a9> in ()
----> 1 f.__next__()

StopIteration:

send法によるアクセス
In [43]: f = gen()

In [44]: f.__next__()
Out[44]: 0

In [45]: f.send('haha')
haha
Out[45]: 1

In [46]: f.__next__()
None
Out[46]: 2

In [47]: f.send('haha')
haha
Out[47]: 3

In [48]:

まとめ
ジェネレータは、前回戻ったときの関数体の位置を記憶する関数です.ジェネレータ関数の2回目(またはn回目)の呼び出しは、その関数の間にジャンプし、最後に呼び出されたすべてのローカル変数は変更されません.
ジェネレータはデータの状態を「覚えている」だけでなく、データの状態を「覚えている」だけでなく、ジェネレータはまた、ストリーム制御構造(コマンドプログラミングでは、データ値だけではない)における位置を「記憶」します.
ジェネレータの特徴:1.メモリの節約2.次の呼び出しに反復すると、使用するパラメータは最初に保持されます.つまり、すべての関数呼び出しのパラメータが最初に呼び出されたときに保持され、新しく作成されたものではなく、すべての関数呼び出しのパラメータが最初に呼び出されたときに保持されます.