Edited at

ジェネレータの使い方

More than 3 years have passed since last update.


ジェネレーター

ジェネレータ関数は簡単に言うと、関数の中の途中でreturnができる感じ。

でも、return的なことをしても終わらないのが特徴。

あと、一つづつ返していきのでメモリもあまり使わない。

python3で追加されたサブジェネレータも解説していきます。

今回作ったコード:https://github.com/KodairaTomonori/Qiita/tree/master/default_module/syntax

実際はreturnの代わりにyieldを使います。

まずは簡単に、ただ単に数を数えるcounterfibonatti数列の生成関数から


基本的な使い方(yield, .__next__(), .close() )


counter_and_fibonati.py

def counter():

num = 0
while True:
yield num
num += 1

def fibonatti():
now = 0
next_num = 1
while True:
yield now
now, next_num = next_num, now + next_num

if __name__ == '__main__':
count = counter()
print('print 0-3, for i in count')
for i in count:
print(i, end=', ')
if i >= 3:
print()
break
print('next_count, count.__next__()')
print(count.__next__())
print('print 5-7, for i in count')
for i in count:
print(i, end=', ')
if i >= 7:
count.close()
print()
print('print fibonatti')
for i in fibonatti():
if i > 100: break
print(i, end=', ')
print(i)



出力


print 0-3, for i in count

0, 1, 2, 3,

next_count, count.__next__()

4

print 5-7, for i in count

5, 6, 7,

print fibonatti

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144



解説

まず、counter関数は簡単で、num=0からスタートして、whileの無限ループには入ります。

1ループごとにyield numnumを返します。

次に、counterが呼び出された?ときにnum+1して、その後yield numを返します。

呼び出されるごとに+1した値が帰ってきます。

使うときは、mainのとこに書いてありますが、for文に入れると1ループごとに順次、値を0,1,2,3を返してくれます。

count = counter()と変数として定義することで、その状態(num)を保存しておけます。

count.__next__()を使うと次の数、つまり4が帰ってきます。

次のfor文もさっき同様に1づつ足して返してくれます。

ですが、さっきとは違い、countの状態はnum=4で止まっているのでこのループでは5から始まります。

で、最後に7以上になったっと気にcount.close()をして、ループを終了させています。

.close()を使うとfor文を抜け、なおかつcountのジェネレータとしての機能がなくなります。

この後に、count.__next__()StopIteration吐き出します。

fibonattiの方はほぼ一緒なので特に問題ないですね。

こっちでは、fibonatti()と直接ジェネレータを置いているので、for文を抜けた後now, next_numはが保存されているジェネレータはどっかに行ってしまいます。

for文で回せるということはlist()で囲むとlistに出来ますが、その場合、ジェネレータ関数内に制限を設けないと無限ループして死ぬので注意です。


呼び出すごとに引数を与えたい時

.send(x)で引数をその都度渡すことができます。

.send(x)yieldの位置にxを与えるため、一回.__next__()を執行しなければなりません。


generator_send.


def generator(step):
val = 0
prev = 0
while True:
if step == None:
step = prev
prev = step
val += step
step = yield val

if __name__ == '__main__':
gen = generator(0)
print(gen.__next__(), end=', ')
for i in [1,2,3,4,5,6]:
print(gen.send(i) , end=', ')
print()



出力


0, 1, 3, 6, 10, 15, 21,



解説

generator関数は、受け取った引数stepvalに足すプログラムです。

最初に、generator(0)初期値を0に設定します。

次に.__next__()を使い、step = yield valまで行きます。

そうすると、0が帰ってきます。

そして次にfor文には入り、1から6まで回しいきます。

gen.send(i)が実行されると、前回step = yield valのところにiが入ります。

つまり、gen.send(i)をやるとstep = iとなります。

そんな感じでループして行きます。ここで、.send()を使わず、.__next__()をするとstep = Noneとなります。


python3の新機能:サブジェネレータ

サブジェネレータは、yield from generatorとやると、generatorを実行してくれます。

generatorはジェネレータ関数です。

これを使うと再帰がとても便利になります。

同じものを含む順列の生成のプログラムをサンプルとして説明します。


ex_permutation.py

def ex_permutation(iterable, now_list=[]):

if not iterable:
yield now_list
return
for i in [iterable.index(i) for i in set(iterable) ]:
yield from permutation(iterable[:i] + iterable[i+1:], now_list + [iterable[i] ])

if __name__ == '__main__':
permu = ex_permutation([2,2,3])
for i in permu:
print(i)



出力


[2, 2, 3]

[2, 3, 2]

[3, 2, 2]



解説

中身はどうでもいいんですが、set()をつかってかぶんないように再帰的にやってるだけです。

で、yield fromで再帰的に書くことができます。

最後にiterableの中身がなくなった時にnow_listを返してくれます。

なんかわかんないけど便利!

これがないとややこしいことになるらしい。


まとめ

yieldで関数の中身の途中で値を返せる。

.close()でジェネレータ関数を終了できる。

.__next__()で次を実行する

.send()で値を渡せる

yield fromは便利。