- HOME
- 本当に誰でも読める!! Pythonソースコードの読み方講座(第11回)yield 文とジェネレータ
2023年7月21日 17:54
本記事は当社が発行しているシミュレーションメールマガジンVol.11の記事です。 シミュレーションメールマガジンの詳細・購読申込はこちら のサポートページから
- Pythonソースコードの読み方講座(第1回) Pythonコードの構成要素
- Pythonソースコードの読み方講座(第2回) classにまつわるコードの読み方
- Pythonソースコードの読み方講座(第3回) キーワード編 1: 構文に関するキーワード
- Pythonソースコードの読み方講座(第4回) キーワード編 2: 続・構文に関するキーワード
- Pythonソースコードの読み方講座(第5回) 関数にまつわるコードの読み方
- Pythonソースコードの読み方講座(第6回) ソースコードに現れる「単語」を読む
- Pythonソースコードの読み方講座(第7回) キーワード編 3:Pythonの生命線?import文について
- Pythonソースコードの読み方講座(第8回)Python コードを俯瞰する 1:コードの領域分割と実行順序
- Pythonソースコードの読み方講座(第9回)Python コードを俯瞰する 2:コードの領域間の「見え方」について
- Pythonソースコードの読み方講座(第10回)Python コードを俯瞰する 3:関数内の関数について
- Pythonソースコードの読み方講座(第11回)yield 文とジェネレータ
- Pythonソースコードの読み方講座(第12回)yield 文とジェネレータ 2
プログラマにもノン・プログラマにも読んでいただきたい「Pythonコードの読み方」講座の第11回です。今回は yield
文を扱います。yield
文はS4のシミュレーションのコア実装に関わるほか、自身でシミュレーションを記述する際にも登場します。この機会にぜひ理解を深めてください。
ジェネレータの基本
yield
が関係するコードの「読み方」としては以下を抑えておけばOKです:
- 関数定義内に
yield
文が含まれている場合、その関数はジェネレータ関数となる - ジェネレータオブジェクトを動作させると次の
yield
文までの処理が実行され、値が返される
早速例を見ていきましょう。
次のコードでは、yield
を含む関数を定義し、呼び出しています:
コード1
def func(): print("1回目の呼び出し") yield 1 print("2回目の呼び出し") yield 2 print("3回目の呼び出し") yield 3 print("しつこい!") gen = func() print(type(func)) print(type(gen))
出力1
class 'function' class 'generator'
yield
を含む関数 (ここでは func
) のことをジェネレータ関数と呼びます。出力の1行目からも、func
が関数であることが確認できます。
ジェネレータ関数を呼び出すと返り値としてジェネレータオブジェクト (ここでは gen
) を得ることができます。出力の2行目から、gen
が generator
のインスタンスであることが確認できます。
ジェネレータオブジェクトは組み込み関数 next
を用いることで yield
までの処理を逐次的に行うことができます。コード1 に続けて次のコード2 を実行してみます:
コード2
v1 = next(gen) print(v1) v2 = next(gen) print(v2) v3 = next(gen) print(v3)
出力2
1回目の呼び出し 1 2回目の呼び出し 2 3回目の呼び出し 3
最初の next
の呼び出しで最初の yield
までの処理が走ります。このとき print
文が実行されたあとに返り値として yield 1
の 1
が得られます。これが3回繰り返された結果、上のような出力が得られます。
この コード2 の実行によって func
で定義した処理のすべての yield
文が行われたことになります。さらにもう一度 next
を呼び出すとどうなるでしょうか?
コード3
next(gen)
出力3
しつこい! --------------------------------------------------------------------------- StopIteration Traceback (most recent call last) in ----> 1 next(gen) StopIteration:
怒られたうえにエラーが起きてしまいました。これは next
の呼び出しによって以下のような処理が行われたためです:
func
で定義した処理が最後まで走り、print
文が実行された- 関数の終わりに到達し、ジェネレータオブジェクトが返す値が無くなったため、そのことを示すエラーが送出された
ジェネレータからの値の取り出し方
ジェネレータオブジェクトから値を取り出すには next
関数以外にも色々な方法があります。
for 文との組み合わせ
コード4
gen = func() for item in gen: print("item の値は", item)
出力4
1回目の呼び出し item の値は 1 2回目の呼び出し item の値は 2 3回目の呼び出し item の値は 3 しつこい!
コード4 では新たにジェネレータオブジェクトを取得しなおし、これを for
文に与えています。以下のような動作をしています:
for
文の各ステップで1つずつyield
の返り値を取得している- 最後まで行って
StopIteration
が返されたらfor
文を抜ける
list 関数との組み合わせ
コード5
gen = func() l = list(gen) print("リストは", l) l2 = list(gen) print("2回目のリストは", l2)
出力5
1回目の呼び出し 2回目の呼び出し 3回目の呼び出し しつこい! リストは [1, 2, 3] 2回目のリストは []
コード5 では gen
を組み込み型の list
に与えることで、gen
の生成する値からなるリストを得ています。 for
文のときと同様、 yield
が返すすべての要素を順に取得できていることがわかります。
注意点として、一度値を取り出した gen
に対してもう一度 list
を作用させても値を取得することはできません。 コード5 においても1回目で値を取りきったため2回目の list
の実行では空のリストが得られていることがわかります。
ジェネレータの使い所
生成すべき値だちを最初に全て計算できる場合、リスト等のデータ型を用いればよく、ジェネレータを使う必要はありません。 値生成器としてのジェネレータの利用価値は、系列長が無限に長い場合や計算時間やメモリ使用量が大きい場合に出てきます。
無限に値を生成するジェネレータ
コード6
def func(val): # 0,1,...val-1 をループして生成しつづける while True: for v in range(val): yield v gen = func(3) for _ in range(10): print(next(gen))
出力6
0 1 2 0 1 2 0 1 2 0
コード6 では与えた範囲の数字を無限にループして生成するジェネレータ関数を定義し、実際に値を取り出しています。 このように、取り出す値の個数が事前に決まっていなくても動作する値生成器を作るためにはジェネレータを使うか、自作クラスを作る必要があります。
非常に大きいファイルの読み込み
実際に生成しうるすべての値を前もって取得することが困難なケースもあります。
例えば非常に大きいファイルがあり、この中のうち "a" から始まる行だけを抽出したい場合を考えます。ジェネレータを用いることで次のように「ファイルを先頭から読み、"a" で始まる行を見つけたら yield
する」プログラムを作ることができます。
コード7
def func(): with open("large_input.txt", "r") as fp: while line := fp.readline(): if not line: break if line.startswith("a"): yield line count = 0 for line in func(): print(line) count += 1 if count==10: break
このような処理を行う際にジェネレータを使わずに最初にファイルを全て読み込むプログラムは、計算時間・メモリ使用量の面で効率の悪いものになってしまいます。
まとめ
今回は yield
文を含むコードについて扱いました。
再掲になりますが、コードを読む上では以下のポイントを抑えておけばOKです:
- 関数定義内に
yield
文が含まれている場合、その関数はジェネレータ関数となる - ジェネレータオブジェクトを動作させると次の
yield
文までの処理が実行され、値が返される
S4のユーザコードの中にも yield
文が現れることがありますが、これらもジェネレータを定義していることになります。yield
を区切りとして処理が部分的に少しずつ実行されていくイメージを持つと、コードの理解につながるかもしれません。
次回はジェネレータが持つ、「処理を一時停止したり再開したりできる」性質を利用してより高度なプログラムを作る例を紹介します。
S4 Simulation System の開発のほか、機械学習・シミュレーション・数理最適化を幅広く扱い、分野を横断した問題解決に取り組んでいる。
入社後に競技プログラミングをはじめ、PGBATTLE2023にて企業の部団体3位入賞。
著書: 「XAI(説明可能なAI)──そのとき人工知能はどう考えたのか?」リックテレコム
趣味: 合唱