関数

【Python】ジェネレータ(generator)関数 ~yieldによる返却~

【Python】ジェネレータ(generator)関数 _yieldによる返却_

Pythonのジェネレータ(generator)関数の使い方について解説します。

ジェネレータ(generator)関数

Pythonでジェネレータ(generator)とは、繰り返しなどで使用できるシーケンスを作成するオブジェクトのことを言います。

例えば、0, 1, 2, 3, ・・・, xと続くような数値シーケンスを使う場合に、長さがxになるようなリストを作成してfor文などで繰り返せばよいですが、xが大きくなってくるとメモリ上にいつか展開できなくなります。

ジェネレータを用いるとシーケンス全体をメモリ上に展開する必要なく、シーケンスの繰り返し処理をすることができます。

ジェネレータは繰り返し処理のたびに最後に呼び出された位置を記憶しており、次に呼び出された時にはその次の値からシーケンスを続けることができます。このため、シーケンス全体をメモリ上に展開する必要がありません。

イメージしづらいかと思いますので、以降の関数定義の例を見ながらイメージをつかんでもらえればと思います。

ジェネレータ(generator)関数の定義方法

yieldによる返却

ジェネレータ(generator)関数は、定義方法は普通の関数とほとんど同じです。ただし、returnの代わりにyieldを使います。以下の簡単な例を使って見ていきましょう。

# generator関数
def generate_char():
    yield "A"
    yield "B"
    yield "C"


if __name__ == "__main__":
    gen = generate_char()
    print(type(gen))

    # Aが出力される
    print(next(gen))
    # Bが出力される
    print(next(gen))
    # Cが出力される
    print(next(gen))
【実行結果】
<class 'generator'>
A
B
C

上記の例は”A”, “B”, “C”という文字列の羅列を順番に返却するようなジェネレータ関数になっています。

gen = generate_char()のようにジェネレータ関数を呼び出すと、返却値のgenはtypeで確認した結果からも分かりますが、generatorクラスのオブジェクトになります。

ジェネレータのオブジェクトは、next関数を使うことで順番に値を取り出すことができます。この時、呼び出し元で最初に呼ばれたときには、関数の最初の「yield “A”」でAが返却され、関数はその位置を記憶します。

次にnext(gen)が呼び出された時には、「yield “A”」ではなく、次の「yield “B”」でBが返却されます。その次も同様で次はCが返却されます。なお、Cまで到達して繰り返しが終了してからnext(gen)を呼び出すと「StopIteration」という例外となります。

ジェネレータ関数のfor文での使用

ジェネレータ(generator)関数は、以下のようにfor文の繰り返しとして指定することができます。

# generator関数
def generate_char():
    yield "A"
    yield "B"
    yield "C"


if __name__ == "__main__":
    # for文でのgenerator関数の使用
    for s in generate_char():
        print(s)
【実行結果】
A
B
C

上記のfor文ではStopIterationまで順にジェネレータ関数の結果がsに代入されていくので繰り返し処理で使用することができます。

rangeと同じ処理をジェネレータ関数で実現

Pythonのfor文などで数値シーケンスをつかって処理する場合には「range」をよく使うかと思います。このrangeは数値シーケンスを返す処理なのでジェネレータで実現されています。

では、ジェネレータを使う例としてrangeと同じような動作をするジェネレータ関数sample_rangeを作ってみます。

# ジェネレータでrangeと同じ動作を実現
def sample_range(start=0, stop=10, step=1):
    num = start
    while num < stop:
        yield num
        num += step


if __name__ == "__main__":
    iter_range = sample_range(0, 5)
    print(type(iter_range))
    print(next(iter_range))
    print(next(iter_range))
    print(next(iter_range))

    print("======================")
    for i in iter_range:
        print(i)
【実行結果】
<class 'generator'>
0
1
2
======================
3
4

ジェネレータのオブジェクトに対してnext関数を実行して順番に数値が取り出せているところからも分かるように、ジェネレータ関数を使うと前回の実行時点の情報を持っており次の処理では、その次から実行されている(前回実行時の数値にstepが追加されて返ってきている)ことが分かります。

また、途中で’===============’という区切りをprintしてから、for文で先ほど作ったジェネレータオブジェクトを指定していますが、前回実行時の2にstepが追加されて3が返ってきていることも分かります。

このように、ジェネレータ関数を使うとシーケンスを必要なタイミングで順に取り出して処理したり、一度にメモリ上に展開できないような無限に続くような数列を扱ったりすることも可能になります。

ジェネレータ内包表記

ジェネレータ関数が返すようなジェネレータオブジェクトは、ジェネレータ内包表記というシンプルな記載方法でも生成することができます。

ジェネレータ内包表記については「ジェネレータ(generator)内包表記の使い方」でまとめていますので興味があれば参考にしてください。

まとめ

Pythonのジェネレータ(generator)関数の使い方について解説しました。ジェネレータ(generator)とは、繰り返しなどで使用できるシーケンスを作成するオブジェクトのことを言います。

基本的な定義方法は、関数のreturnをyieldに置き換えるといった簡単な変更になっています。ジェネレータを作成するとシーケンスをfor文の繰り返し処理でも使用ができたり、必要なタイミングでシーケンスの値をnextで順に取り出して処理したりできます。

ジェネレータは、一度にメモリ上に展開できないような無限に続くような数列を扱ったりすることもできて便利ですので是非使い方は覚えておいてもらうとよいかと思います。