#ジェネレーター
ジェネレータ関数は簡単に言うと、関数の中の途中でreturn
ができる感じ。
でも、return
的なことをしても終わらないのが特徴。
あと、一つづつ返していきのでメモリもあまり使わない。
python3で追加されたサブジェネレータも解説していきます。
今回作ったコード:https://github.com/KodairaTomonori/Qiita/tree/master/default_module/syntax
実際はreturn
の代わりにyield
を使います。
まずは簡単に、ただ単に数を数えるcounter
とfibonatti
数列の生成関数から
##基本的な使い方(yield, .__next__(), .close() )
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 num
でnum
を返します。
次に、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__()
を執行しなければなりません。
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
関数は、受け取った引数step
をval
に足すプログラムです。
最初に、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
はジェネレータ関数です。
これを使うと再帰がとても便利になります。
同じものを含む順列の生成のプログラムをサンプルとして説明します。
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
は便利。