おぼえがき

過ちては則ち改むるにうんぬんかんぬん

ジェネレータ関数とジェネレータ式について(Python)

f:id:feci:20210726231508j:plain

Python3

 

Python3.8.5におけるジェネレータ関数について。

#①通常のイテレータを使ったfor文
list = ['おう''夏だぜ''おれは元気だぜ']

for aaa in list:
  print(aaa)      #3行にわたってlistの文字列が表示される


#②ジェネレータ関数を使ったパターン
def bbb():        #yieldを記述してジェネレータ関数bbbを作成
  yield 'おう'
  yield '夏だぜ'
  yield 'おれは元気だぜ'

for ccc in bbb():
  print(ccc)      #関数bbb内の値が上から順に3行にわたって表示される

ddd = bbb()       #ジェネレータ関数bbbのオブジェクトを変数ddd内に格納(nextを使うにはこの処理が必須)

print(next(bbb()))  #関数bbb内の0番目の値を得る
print(next(bbb()))  #再度関数bbb内の0番目の値を得る(何度繰り返しても0番のみ)

print(next(ddd))    #関数bbb内の0番目の値を得る(関数bbbは0番目で待機)
print(next(ddd))    #関数bbb内の1番目の値を得る(関数bbbは1番目で待機)
print(next(ddd))    #関数bbb内の2番目の値を得る(関数bbbは2番目で待機)
print(next(ddd))    #関数bbb内に3番目の値がないのでStopIterationとなる(例外処理でプログラムの終了)

print(next(list)    #変数や定数は関数ではないので実行できない(SyntaxError)

ジェネレータ関数はイテレータを作成するための関数です。

関数内にyieldと記述してやることで、Pythonがその関数をジェネレータ関数であると認識します。

 

ちなみにイテレータとは、「要素を1つずつ取り出せるオブジェクト」のようなもののことです。

リストやタプル、辞書、集合などがイテレータに該当します。

 

ちなみにジェネレータ関数の返り値もまたイテレータとなるのですが、このジェネレータ関数の返り値はジェネレータイテレータとも呼ばれます。

 

②のプログラムは、①をジェネレータ関数にしたパターンです。

ジェネレータ関数を変数内に格納してその変数を next(変数名) という形で実行してやると、そのジェネレータ関数の0番目の値から1つずつ順番に値を得ることができます。

 

なおこのような next() でジェネレータ関数の値を1行ずつ得ていく方法は、ジェネレータ関数そのものには使用できません。

一応0番目の値を得ることはできるのですが、なんどnextを実行しても0番目の値を得続けるだけで永久に次の1番目の値を得ることはできません。

 

なお、3つしか値のないジェネレータ関数に4回目の next() を実行すると、StopIterationの例外処理が起きてしまし、そこでプログラムが強制終了します。

 

 

ジェネレータ式について。

#③ジェネレータ式を使ったパターン
eee = (x for x in 'おう 夏だぜ おれは元気だぜ' . split()) #split()を記述すればこのようにも書ける
fff = (y for y in ['おう''夏だぜ''俺は元気だぜ'])  #split()を記述しなければこのようになる

print(eee)        #変数eeeに格納された無名ジェネレータ関数オブジェクトそのものを出力
print(next(eee))  #おう(関数eeeは0番目で待機)
print(next(eee))  #夏だぜ(関数eeeは1番目で待機)
print(next(eee))  #おれは元気だぜ(関数eeeは2番目で待機)


#④既存のリストをジェネレータ式でジェネレータ関数オブジェクトにするパターン
list = ['おう''夏だぜ''おれは元気だぜ']
ggg = (z for z in list)

print(next(ggg))  #おう(関数gggは0番目で待機)
print(next(ggg))  #夏だぜ(関数gggは1番目で待機)
print(next(ggg))  #おれは元気だぜ(関数gggは2番目で待機)

ジェネレータ式は、簡易的にジェネレータ関数のオブジェクトを作成する方法です。

上記③や④のような記述法(リスト内包表記)で、無名のジェネレータ関数オブジェクトが作成できます。

 

これはどういうことかと言うと、③のプログラムのeeeはあくまでも、3つの値の入った無名のジェネレータ関数のオブジェクトが格納された変数でしかありません。

eeeそのものはジェネレータ関数ではありません。

 

その証拠に、変数eeeを出力すると <generator object <genexpr> at 0x00000226DA704740> と表示されます。

さらに上記②のプログラムではジェネレータ関数はいったん変数に格納しないと next() を正常に処理できなかったのに対して、eeeはそのままの形で next() が正常に処理されてしまいます。

 

このように、ジェネレータ式が作るのはジェネレータ関数そのものではありません。

ジェネレータ式が作るのは、あくまでも無名のジェネレータ関数オブジェクトを格納した変数です(おそらくこの認識であっているはずです)。

 

ジェネレータ式の定形については、上記の③のプログラムを参照してください。

 

 

以上です。