ジェネレーター

ジェネレーターは関数を一時停止させ、途中経過の結果を返すことができる機能です。

通常、関数やメソッドは結果を返す時にはreturnを利用しますが、それをyieldに変えることでジェネレーターにできます。

# 普通の関数
def normal_function():
    return 'hello world'

# ジェネレーター
def generator():
    yield 'hello world'

ジェネレーターを利用することで、関数の途中まで進めて結果を返し、任意のタイミングで処理を再開できます。

例:


def generator(n):
   for i in range(n):
     yield 'hello_{}'.format(i)

# ジェネレーターを実行するとジェネレーターオブジェクトが返ってきます。
gen = generator(3)

# nextを使って途中結果を取得
value = next(gen)
print(value)  # => 'hello_0'

value = next(gen)
print(value)  # => 'hello_1'

value = next(gen)
print(value)  # => 'hello_2'

# 返せるものがなくなると StopIteration が発生します。
next(gen) # => 例外 StopIteration が発生

最初に yield が呼ばれると関数はそこで処理が停止し、次にnextが呼ばれるまでは実行されません。返すべき値がなくなった場合は StopIterationの例外が発生します。

ジェネレーターオブジェクトlist型などと同様に、forループで扱うことができます。

gen = generator(3)
for value in gen:
    print(value)

実行結果:

hello_0
hello_1
hello_2

どのようなメリットがあるのか?

ジェネレーターは関数の中で処理を停止し、任意のタイミングで再開できます。つまり関数の中で状態を持つことができます。この特性を利用することで、巨大なデータを扱う際にメモリー使用量を節約することに活用できます。

例として、数値データが入ってるlistのデータを引数で与えると、それぞれの数値を2乗して返すような関数を考えてみます。

def squaring_list(numbers):
    """ numbersの各要素を二乗する """
    result = []
    for number in numbers:
        result.append(numer ** 2)
    return result

numbers = [1, 2, 3, 4, 5]

for square_number in squaring_list(numbers):
    print(square_number)

実行結果:

1
4
9
16
25

squaring_list関数はそれぞれの要素を2乗した結果をresultという変数に一旦全て保存して最後にreturnで返しています。

numbersの要素数が10万、100万と膨大な数になると、resultには大量の計算結果が溜まっていき、その分だけメモリーの使用量が増大します。最悪マシンのメモリーが足りなくなってプログラムが止まってしまう可能性もあります。

そこでメモリーを節約するために、ジェネレーターを活用します。

def squaring_list(numbers):
    """ numbersの各要素を二乗する """
    for number in numbers:
        yield number ** 2

先ほどの関数との大きな違いは、forループの中で計算をしたらyieldですぐに結果を返すという点です。こうすることでresult変数に計算結果を溜め込む必要がなくなるので、前の関数に比べてその分メモリーを節約できます。

またジェネレーター化しても、元の関数と利用方法がまったく同じにできるというのも嬉しい点です。

メモリーが節約できるのはジェネレーターを活用するほんの一例ですが、特性を理解して賢く使いましょう。

ジェネレーター式

ジェネレーターはyieldを使わずにとして表現することもできます。簡単なものであれば、関数のスタイルよりも簡潔に記述できるので選択肢の1つとして覚えておくと良いでしょう。

numbers = [1, 2, 3, 4, 5]

# for文を ()  囲むだけでジェネレーターが生成されます。
gen = (n ** 2 for n in numbers)

for square_number in gen:
    print(square_number)

公式ドキュメント

詳細は、9.9. ジェネレータ (generator)ジェネレータを参照してください