Pythonインタラクティブガイド - ステップ3 関数 (10) - ジェネレータ関数
- 本講座「Pythonインタラクティブガイド」は、手を動かしながらPythonプログラミングの基礎を学べる実践的な講座です。
- 「スタイルガイド」では、Pythonで読みやすくきれいなコードを書くためのガイドライン(PEP8)を主に紹介しています。
- 各コード例はその場で実行して結果を確認できます。
ページ再読み込みで元に戻るので、自由に試してみてください。
「ステップ3 関数」の続きです。
3.9. ジェネレータ関数
今回は、データを効率的に扱うための特殊な関数であるジェネレータ関数について学んでいきます。
3.9.1. ジェネレータ関数とは
「2.2.3. 内包表記」では、ジェネレータ式について扱いました。
ジェネレータ式は、以下の性質を持つ「ジェネレータ(オブジェクト)」を返します:
- イテラブル(繰り返し可能)である
- 値を一度だけ順に取り出せる「使い切り」のオブジェクト
- 値をすべて一度に生成するのではなく、必要に応じて一つずつ生成するためメモリ効率が良い
今回扱う ジェネレータ関数 も、同様にジェネレータを返す関数です。
- 上記ジェネレータ関数で生成されたジェネレータは、コードを順に実行し、
yield
文に到達するとその時点で指定された値を返します。 この動作を関数が終了するまで繰り返します。 - 関数が終了すると値の生成も終了します。
next()
関数を用いると、ジェネレータから次の値を1つずつ取得できます。
- すべての値を生成後に
next()
を呼び出すと、StopIteration
例外が発生します。
上記の例から分かるように、ジェネレータ関数が出力するジェネレータは以下の特徴を持ちます:
next()
関数を呼び出すと、最初のyield
文までコードを実行して値を返す- 次に
next()
が呼ばれると、前回のyield
文の直後から実行が再開され、次のyield
文までコードを実行して値を返す - 上記2パターンのいずれの場合も、次の
yield
文が存在しない場合は、コードを最後まで実行した後にStopIteration
例外を発生させる
実はfor
文は内部で「next()
関数を繰り返し呼び出し、StopIteration
が発生したら処理を終了する」という仕組みで動いています。
引数n
を受け取り、0 ~ n
の偶数を順番に生成するジェネレータ関数 even_numbers
を作成しましょう。
解答例
再帰関数の項でも扱った、「コラッツ数列」を生成するジェネレータを作成しましょう。
コラッツ数列とは?
コラッツ数列とは、入力された正の整数 n
から始めて、以下のルールに従って 1 に到達するまで続ける数列 のことです。
n
が 1 の場合: 処理を終了するn
が偶数の場合: 次の数はn / 2
n
が奇数の場合: 次の数は3n + 1
この手順を繰り返すと、任意の数で最終的に1に到達すると予想されています(「コラッツ予想」)。
(例: n=6
の場合、 6 → 3 → 10 → 5 → 16 → 8 → 4 → 2 → 1)
問題
引数 n
を受け取り、コラッツ数列を順に生成するジェネレータ collatz_generator
を実装しましょう。
解答例
3.9.2. 無限ジェネレータ
ジェネレータ関数を使用すると、リストでは表現できないような無限に続く数列も生成できます。
無限ジェネレータをfor
ループで使用する場合は、何らかの条件で中断する必要があります:
3.9.3. 複数のジェネレータを組み合わせる (yield from
)
yield from
構文を使用すると、イテラブル(リストやジェネレータなど)から値を順に生成できます。
これを用いて、複数のジェネレータを組み合わせたジェネレータの作成が簡潔に行えます:
yield from
を使わない場合、以下のように冗長な記述が必要になります:
yield from
は単に短縮構文というだけでなく、後に紹介するsend
、throw
、close
といった操作も内部のジェネレータへそのまま引き渡す働きを持っています。
yield from
を使って、以下の条件を満たすジェネレータ関数 combined_sequence
を作成しましょう:
- 引数として3つの異なる数値リストを受け取る
- それぞれのリストの要素を順番に生成する
- 各リストの間に区切り値として-1を生成する
解答例
3.9.4. ジェネレータの高度な機能
ここまではジェネレータを主に「値を生成するオブジェクト」として使用してきました。 次に紹介する機能を用いると、外部からジェネレータにデータを送信でき、処理の流れを双方向で制御できるようになります。
ジェネレータに値を送る (send
メソッド)
send
メソッドを使用すると、ジェネレータに値を送信することができます。
送信された値は、以下のように yield
式の戻り値として受け取ります:
上記文では、以下の2つの操作を行っています:
- この文に到達すると、「出力値」を出力して実行を一時停止する
- この文で停止時に
send
メソッドが実行されると、その引数を「変数」に代入して、次のyield
文までコードを実行する
上記のようにしてsend
メソッドの値を受け取るため、以下に注意してください:
send
メソッドを使用するには、そのジェネレータがyield
文で停止している必要があります。
そのため、最初にsend
メソッドを使用する前に、next()
関数を用いてyield
文まで進めておく必要があります。- 上記文で停止時に
next()
関数が使用されると、「変数」にはNone
が代入されます。
「受け取った値の統計値を画面出力するジェネレータ」を生成する関数 stats()
を作成しましょう。
- 数値を
send
メソッドで受け取ります - 受け取るたびに、これまで受け取った値の合計・個数・平均を画面出力します
解答例
ログ監視を行うジェネレータ関数 error_monitor()
を作成しましょう。
- 文字列を
send
メソッドで受け取ります "ERROR"
が 3 回連続 で現れたら"ALERT"
を返してください- それ以外は
None
を返してください
解答例
ジェネレータに例外を送る (throw
メソッド)
send
メソッドが通常のデータを送信するのに対し、throw
メソッドを用いるとジェネレータに例外を送ることができます。
throw
メソッドを呼び出すと、ジェネレータが最後に停止したyield
文の位置で指定した例外が発生します。
例外を送信することで、ジェネレータの実行フローを制御できます。
これはジェネレータ内でエラー処理やリソースのクリーンアップなどを行う際に役立ちます。
(エラー処理については後ほど詳しく解説します)
ジェネレータを終了する (close
メソッド)
close
メソッドを使用すると、ジェネレータを明示的に終了できます:
close
メソッドは、ジェネレータ内部で使用しているリソースの解放が必要な場合などに使用されます。
まとめ
今回はジェネレータ関数について学びました。
-
ジェネレータ関数は、
yield
文を使ってデータを一つずつ生成する特殊な関数です -
メモリ効率が良く、大量のデータや無限のデータ列でも効率的に扱えます
-
ジェネレータの主な特徴:
- 値を一度だけ順に取り出せる「使い切り」のオブジェクト
- 必要に応じて値を一つずつ生成する「遅延評価」方式
for
文やnext()
関数で値を取り出せる
-
高度な機能:
yield from
:他のイテラブルから値を生成する簡潔な方法send()
メソッド:ジェネレータに値を送信して双方向通信を実現throw()
メソッド:ジェネレータに例外を送出して制御close()
メソッド:ジェネレータを明示的に終了
ジェネレータ関数は、大規模データの処理やストリーム処理など、様々な場面で活用できる強力な機能です。
特に、メモリ使用量を抑えながら大量のデータを扱う必要がある場合に便利です。