関数

【Python】ジェネレータ関数とジェネレータの基本

【Python】ジェネレータ関数とジェネレータの基本

Pythonのジェネレータ関数とジェネレータの基本について解説します。

ジェネレータ関数とジェネレータ

Pythonにおけるジェネレータ関数とは、yield文を使って値を返却する関数であり、その実行によってジェネレータというオブジェクトが作成されます。ジェネレータはイテレータの一種であり、逐次的に値を生成して返すオブジェクトのことを言います。なお、イテレータについては「イテレータの基本」を参考にしてください。

このため、ジェネレータを使うとシーケンス全体をメモリ上に展開せず、無限のシーケンスであっても必要な時に必要な値だけを生成することができ、メモリ使用量を大幅に削減できます。

この記事では、ジェネレータ関数とジェネレータについて基本の使い方を紹介します。

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

ジェネレータ関数は、通常の関数とほぼ同じ定義方法ですが、return文の代わりにyield文を使用します。

# ジェネレータ関数
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

上記のgenerate_char関数は、"A""B""C"という文字列を順に返すジェネレータ関数です。genはジェネレータのオブジェクト(<class 'generator'>)となります。

ジェネレータはイテレータの一種であり、__next__()メソッドが実装されているため、next関数を使って順番に値を取り出せます。

ジェネレータ関数はyield文まで実行され、その位置を記憶します。次に呼び出されると、その位置から実行を再開します。例えば、最初の呼び出しではyield "A"が実行され、次の呼び出しではyield "B"が実行されます。

yield "C"までが実行され、さらに次の呼び出しで返す値がなくなると、ジェネレータは、StopIteration例外を送出します。

ジェネレータはイテレータの一種のためfor文で使用できます。

# ジェネレータ関数
def generate_char():
    yield "A"
    yield "B"
    yield "C"


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

上記結果のようにジェネレータが生成する値("A""B""C")を順に取り出せていることが分かります。

rangeをジェネレータ関数で実現する

Pythonのfor文では、数値シーケンスの生成にrangeをよく使用します。rangeはジェネレータ関数として実現されています。

ジェネレータ関数の使用方法を覚えるために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

上記のsample_range関数は、rangeと同じ動作をするジェネレータ関数です。iter_rangeは、ジェネレータのオブジェクト(<class 'generator'>)となっています。

そのため、nextで値を順に取り出せていることが分かります。また、途中で「"==="」のようなprintの中断を入れていますが、次の呼び出しでは後続の値から取り出しができていることが分かります。

ジェネレータ関数を使用することで、一度にメモリ上に展開できないような無限に続くような数列なども実現可能になります。

ジェネレータ内包表記

ジェネレータは、ジェネレータ内包表記というシンプルな記載方法でも生成することができます。ジェネレータ内包表記については「ジェネレータ内包表記の使い方」を参考にしてください。

まとめ

Pythonのジェネレータ関数とジェネレータの基本について解説しました。

ジェネレータ関数は、yield文を使って値を返却する関数であり、ジェネレータオブジェクトを作成します。ジェネレータはイテレータの一種なので逐次的に値を生成することができます。この記事では、ジェネレータ関数の定義方法の例とジェネレータの使用例を紹介しました。

ジェネレータは一度にメモリ上に展開できないような無限に続くシーケンスを扱うこともできて便利です。ぜひ、使い方を覚えてもらいたいと思います。