最終更新
Python 3.11.5
Pythonのジェネレータは、一言でいえば「イテレータを生成する機能」です。使ったことがなくとも、コードリーディングをしているときにときどき見かけるので、覚えておくと便利な機能となります。
yieldはジェネレータ関数を作成するときに使う「処理を終了せず返り値を返す予約語」のことです。
実際に手を動かしてその機能を紹介していきます。
HackATAは、エンジニアを目指す方のためのプログラミング学習コーチングサービスです。 経験豊富な現役エンジニアがあなたの学習をサポートします。
✓ 質問し放題
✓ β版公開中(2025年内の特別割引)
ジェネレータの説明を始める前に、yieldとreturnの違いを簡単に説明します。
yield 関数の処理を停止して値を返すreturn 関数の処理を終了して値を返すyieldが「停止」となっているのは、「再開」が可能だからです。
まずは簡単なジェネレータ関数の例を作ってみます。
このスクリプトを実行すると、以下のような結果が得られます。
1つ目のジェネレータオブジェクトがいわゆる「繰り返せるオブジェクト(イテラブルオブジェクト)」であり、for文で取り出すことも可能です。
以下でも1, 2, 3の結果が得られます。
PythonのList型へ変換したい場合はlist()関数を使います。
結果は以下です。
一度生成したジェネレータの中にオブジェクトを追加する場合はsend()関数を使います。
これの出力結果は以下です。
この処理の流れを説明すると以下のようになります。
next()で2が返されるsend()で値が送られた場合、変数vに格納されるsend()で値を送った場合、変数resultに格納されるresultまで返し終わった後にsend()しようとしてもエラーが出るジェネレータに内包されるオブジェクトの数に限りがある場合、次の要素を取り出すnext()関数を限界以上に実行するとエラーが出ます。
結果は以下です。
エラーハンドリングしたい場合は以下のように書きます。
for文などで繰り返し処理でオブジェクトを処理する場合、このように例外発生でbreakするなどします。
ジェネレータ内でreturnを使って値を返した場合、StopIteration例外が発生します。
これが出力する結果は以下です。
print(gen.send(8))以降の処理はreturnされているため発生しません。
List型と同じく、内包表記でジェネレータを書けます。List型だと[]ですが、ジェネレータの場合は()で囲みます。
これは1 ~ 11の範囲で階乗のジェネレータを作成し、5までの取り出したケースとなります。出力結果は以下です。
ほとんど内包表記で書くことはありませんが、こういった使い方も可能です。
今までジェネレータについて紹介してきましたが、「一体いつ使うのか」「List型でよくないか」と感じる人もいることでしょう。
いくつかのユースケースを紹介します。
たとえば、巨大なCSVファイルがあったとします。これをreturnで返す場合とyieldで返す場合(ジェネレータ関数)で比較してみましょう。
returnを使った関数がList型変数dataへ行を追加して返すのに対し、yieldを使うと行ごとに返せます。
これはメモリ消費量の観点で非常に効率的です。
次にデータベース接続をするケースを見てみましょう。Pythonのデータベース系ライブラリのSQLAlchemyを使います。
データベース接続でreturnではなくyieldを使う理由は、CSVファイルと同じく多大なデータ量になった際、メモリを効率的に使うためです。
データベース接続によって得られるデータは、たいていの場合はイテラブル(繰り返し処理できるオブジェクト)なのでジェネレータが有効となります。
他にもジェネレータの利用が有効なシーンはたくさんあります。コードを書くと長くなるため、以下に列挙します。
なかなか使うシーンを想像しづらいジェネレータですが、多くのWebアプリケーションフレームワークやライブラリで処理の効率化を考慮して利用されています。
自身で実装するアプリケーションやツールでも、効率的に利用できるケースがあったら積極的に使っていきましょう。
メモリ消費を抑える実装は、アルゴリズムや設計といった基礎的なスキル以外にも、こういった言語の機能でサポートされていることがあるので、ぜひ覚えておきたいところです。