TL;DR
英語の generate は「生成する、生む」という意味がある。Python におけるジェネレーターはイテレータ(for 文で繰り返せる)の一つである。
ジェネレーターを利用すると、関数の途中で処理を中断し値を返すことができる。これを yield と呼ぶ。その後は処理を再開することができる。
ジェネレーターの作成方法
ジェネレーターの作成方法は以下の 2 つある。
- ジェネレーター関数
- ジェネレーター式
内部で yield を使う関数をジェネレーター関数、内包表記を使うものをジェネレーター式という。
ジェネレーター関数のポイント
- yield 式を使う関数
- for 文で利用できるイテラブル
- リスト内の要素数が増えてもメモリ使用量を節約できる
- 呼び出されるたびに処理を記憶する
- next 関数で次の値を取り出せる
- next 関数が呼ばれるたびに、関数の中の処理が次の yield まで進む
ジェネレータ (generator) — Python 3.9.4 ドキュメント
yield 文を含む関数はジェネレーターと呼ばれ、通常の関数とは違った動作をする。関数は return を利用し値を一つを返すが、return を yield に変えることで複数の値を一つずつ作り出すジェネレーターになる。
ジェネレーターを使えば、関数の途中まで処理を進めフリーズする。そして任意のタイミングで処理を再開することができる。以下のコードは、next が呼ばれるまで処理が中断されている例である。値がなくなると例外 StopIteration が返る。
def g():
yield 1
yield 2
yield 3
a = g()
print(next(a))
print(next(a))
print(next(a))
# 1 → 一度中断
# 2 → 一度中断
# 3 → 一度中断
# StopIteration → 値がなくなると例外
以下の例だと next() で呼び出す度に、 100 加算されて終了している。再度、next()を呼び出すと、前の処理が記憶されており、次の処理が再開されて停止している。
def g(n):
while n:
print(n + 100)
yield n
n -= 1
a = g(5)
next(a)
next(a)
# 105
# 104
# 4
for
for 文でも利用可能である。
def g(n):
while n:
yield n
n -= 1
for i in g(3):
print(i)
# 3
# 2
# 1
ジェネレーター式
ジェネレーター式を用いて、内包表記を使いイテラブルからジェネレーターを作ることもできる。() で囲む。
これはリスト内包表記。全て計算された。
a = [1, 2, 3, 4, 5]
x = [i+5 for i in a]
x
# [6, 7, 8, 9, 10]
[] を () に変えるとジェネレーター式になる。ジェネレーター式は、計算されない。待機している。
a = [1, 2, 3, 4, 5]
x = (i+5 for i in a)
x
# <generator object <genexpr> at 0x7fdb069b5ed0>
next()を呼び出すと、一つずつ計算される。
a = [1, 2, 3, 4, 5]
x = (i+5 for i in a)
x
print(next(x)) # 6
print(next(x)) # 6 7
リストにすると全て計算される
a = [1, 2, 3, 4, 5]
x = (i+5 for i in a)
x
list(x)
# [6, 7, 8, 9, 10]