Pythonのジェネレーターの使い方
Pythonにはジェネレーターという仕組みがあります。
Pythonのジェネレーターはリストを返す関数のようなものなのですが、関数とは違い「return文」では値を返さず「yield文」でリストへ値を追加するイメージです。
Pythonのジェネレーター
Pythonのジェネレーターはリストを返す関数と似ていますが「return文」はなく、リストへ値を追加するタイミングで「yield文」を呼び出して値を保存します。
ジェネレーターで作られた関数は呼び出しても値は返さず、ジェネレーターの「__next__()」メソッドが呼び出されたるたびにに先頭から順に値を返します。
またジェネレーターが返すオブジェクトの型は「generatorクラス型」になります。
def counter() :
    for i in range( 1, 6 ) :
        wk = "{}回目の呼び出し".format( i )
        yield wk
f = counter()
print( "type={}".format( type( f )))
print( f.__next__() )
print( f.__next__() )
print( f.__next__() )
と試しに3回生成したジェネレーターの「__next__()」メソッドを呼び出してみます。
結果は
type=<class 'generator'> 1回目の呼び出し 2回目の呼び出し 3回目の呼び出し
と無事3回呼び出せます。
次にfor文でイテレーターのように呼び出してみます。
(前回のソースの後ろにfor文を追加します。)
def counter() :
    for i in range( 1, 6 ) :
        wk = "{}回目の呼び出し".format( i )
        yield wk
f = counter()
print( "type={}".format( type( f )))
print( f.__next__() )
print( f.__next__() )
print( f.__next__() )
print("-----------")
for i in f :
    print( i )
結果は
type=<class 'generator'> 1回目の呼び出し 2回目の呼び出し 3回目の呼び出し ----------- 4回目の呼び出し 5回目の呼び出し
となります。
ここで重要なのはジェネレーターに関しては、呼び出し側で「これまでに何度呼び出した」とか「次は何回目」と記憶して置く必要はなく、それらはジェネレーター側で記憶してくれています。
ちなみに最初から最後までをfor文で呼び出すことも当然できます。
def counter() :
    for i in range( 1, 6 ) :
        wk = "{}回目の呼び出し".format( i )
        yield wk
f = counter()
print( "type={}".format( type( f )))
for i in f :
    print( i )
上記のコードの実行結果は
type=<class 'generator'> 1回目の呼び出し 2回目の呼び出し 3回目の呼び出し 4回目の呼び出し 5回目の呼び出し
になります。
ジェネレーターの終了
ジェネレーターの終了は__next__()メソッドを呼び出したタイミングで次が無ければ「StopIteration」が発生します。
def counter() :
    for i in range( 1, 6 ) :
        wk = "{}回目の呼び出し".format( i )
        yield wk
f = counter()
print( "type={}".format( type( f )))
print( f.__next__() )
print( f.__next__() )
print( f.__next__() )
print( f.__next__() )
print( f.__next__() )
print( f.__next__() )    #この__next__()はリストがもうない
このコードを実行すると
type=<class 'generator'>
1回目の呼び出し
2回目の呼び出し
3回目の呼び出し
4回目の呼び出し
5回目の呼び出し
Traceback (most recent call last):
  File "main.py", line 14, in <module>
    print( f.__next__() )    #この__next__()はリストがもうない
StopIteration
と「StopIteration」が発生し、処理が終了します。
ジェネレーターのメリット
ジェネレーターのメリットはメモリの消費量を抑えられます。
ジェネレーターは大きなリストを作成する必要がある場合などは事前にメモリを固定で確保する必要がないので、結果的にメモリの消費量が抑えられます。
ジェネレーターのデメリット
ジェネレーターのデメリットの1つに「途中からの値を取得できない」と言ことです。
リストなどなら「lst[ 2 ]」といきなり3番目のデータにアクセスできますが、ジェネレーターはできません。(順次先頭からのアクセスです)。
まとめ
Pythonのジェネレーターはわかりにくい概念の処理ですが、呼び出すタイミングでリストを作成する関数のようなものと思えば、使いやすいかもしれません。






