yieldとは?
yieldは、Pythonの関数内で使う特殊なキーワードです。関数の処理を途中で一時停止し、呼び出し元に値を一つずつ返す機能を持っています。
通常のreturn文は、値を返すと同時に関数の処理が完全に終了してしまいます。一方、yieldを使うと、値を返した後も関数は終了せず、次の呼び出しのために処理を停止した状態(中断状態)で待機します。
yield文を含む関数はジェネレーター関数と呼ばれ、実行すると「ジェネレーターオブジェクト」というものを作ります。
ジェネレーターのメリットは、メモリ効率が非常に良いことです。例えば、100万個の数値が入ったリストを作ると、100万個すべてのデータを一度にメモリ上に用意する必要があります。
yieldを使ったジェネレーターなら、呼び出されるたびに値を1個ずつ作り出して返すため、使用するメモリは最小限で済みます。
yieldの特徴
- return文と違い、値を返した後も関数の状態を保持したまま処理を一時停止する
- yield文が使われた関数はジェネレーター関数と呼ばれる
- ジェネレーター関数は実行するとジェネレーターオブジェクトを返す
- 必要なタイミングで値を1個ずつ生成するため、メモリの消費を大幅に抑えられる
- 大容量のファイル処理や、終わりがない連続データを扱う処理に適している
【関連】
Pythonをもっと詳しく学ぶならpaizaラーニング
基本構文
yieldを使ったジェネレーター関数の基本的な書き方を見ていきましょう。通常の関数定義(def文)の中でyield文を使うと、その関数は自動的にジェネレーター関数になります。
ジェネレーター関数を呼び出しても、すぐには実行されず、ジェネレーターオブジェクトが返されます。このオブジェクトに対してnext関数を呼び出すか、for文で繰り返し処理を行うことで、yieldまで実行が進み値が返されます。次にnextが呼ばれると、前回のyieldの次の行から実行が再開されます。
まずは、yieldで数値を3回返す、最もシンプルなジェネレーター関数の例です。next関数を使って値を一つずつ取り出してみます。
出力結果
1
2
3この例では、ジェネレーターオブジェクト(gen)に対してnext関数を呼び出すたびに、simple_generator関数内の処理が次のyieldまで進むため、yieldの値が順番に出力されます。まず、gen = simple_generator()では関数は実行されません。1回目のnext(gen)でyield 1が実行されて1が返り、処理は一時停止します。2回目のnext(gen)で停止した箇所から再開し、yield 2が実行されて2が返ります。3回目も同様に3が返されます。
次に、for文を使ってジェネレーターからすべての値を一度に取り出す方法です。リストの要素をfor文の中で一つずつyieldで返します。
出力結果
イヌ
ネコ
ウサギこの例では、for pet in animal_generator()のループが始まるタイミングでジェネレーターが作られ、自動的にnext()が呼び出され続けるため、リストの要素が順番に出力されます。
for文は、ジェネレーターが値を返さなくなる(StopIterationという例外が発生する)まで、内部でnextを呼び出し続けます。そのため、next関数を自分で呼び出すよりも簡単に、ジェネレーターのすべての値を取り出すことができます。
実用例
ここからは、yieldを使った実践的なコード例を紹介します。yieldがどのような場面で役立つのか、具体的な使い方とともに解説します。メモリ効率を重視した処理や、大量のデータを扱う場合に特に有効なパターンです。さまざまなパターンを通じてyieldの活用方法を見ていきましょう。
連続した数値を生成するカウンター
指定した範囲の数値を順番に生成するジェネレーター関数です。組み込みのrange関数と似た動作をしますが、独自の処理を追加できます。
出力結果
1匹目の動物
2匹目の動物
3匹目の動物この例では、count_animals(3)がfor文で呼び出され、whileループが3回まわるため、3つの文字列が順番に出力されます。
まず、countが1の状態でyield f'{count}匹目の動物'が実行され、「1匹目の動物」を返して一時停止します。次のループで処理が再開し、countが2になり、「2匹目の動物」を返します。max_countに達するまで繰り返し、countが4になるとwhileループが終了し、ジェネレーターも終了します。
フィボナッチ数列の生成
フィボナッチ数列(前の2つの数を足した数が次の数になる数列)のように、計算で求められる連続データを生成する例です。呼び出し側(for文)で必要な分だけ値を取り出せます。
出力結果
ネコが0匹
ネコが1匹
ネコが1匹
ネコが2匹
ネコが3匹
ネコが5匹
ネコが8匹
ネコが13匹
ネコが21匹
ネコが34匹この例では、フィボナッチ数列の計算結果(a)がlimit(50)を超えるまで、whileループ内でyieldが実行され続けるため、条件(50未満)にあてはまる計算結果が文字列として出力されます。
まず、a(0)がyieldで返されます。次にa, b = b, a + b(aが1、bが1)となり、次のループでa(1)がyieldされます。この計算とyieldをaがlimitを超えるまで繰り返します。
ファイルの行ごと読み込み
大容量のファイルを一度にメモリに読み込むことなく、1行ずつ処理するパターンです。ログファイルの解析などに有効です。(今回は実際の読み込み処理は行わず、ファイル処理を模したリストを代わりに使っています。)
出力結果
イヌ
ネコ
ウサギこの例では、read_animal_file関数が本来「ファイルを1行ずつ読み込む処理」を表しています。yieldを使うことで、巨大なファイルでも全内容を読み込まずに必要な行だけ順に処理でき、メモリ消費を抑えられます。
with open(...)でファイルを開き、for line in file:でファイルの内容を1行ずつ読み込みます。yield line.strip()は行末の改行などを取り除いたテキストを返し、一時停止しながら次の行へ進みます。
今回は実際のファイルは使わず、['イヌ', 'ネコ', 'ウサギ']を行データとして代用していますが、行ごとに処理する構造自体は同じです。
リストの要素を変換しながら返す処理
既存のデータを変換しながら1つずつ返すジェネレーターです。データの前処理やフィルタリングに活用できます。
出力結果
かわいいハムスター
かわいいインコ
かわいいカメこの例では、関数がリストの要素を一つずつ受け取り、先頭に「かわいい」という文字列を付けてyieldで返すため、加工された文字列が順番に出力されます。
まず、for animal in animal_list:でリストの最初の要素「ハムスター」が取り出されます。yield f'かわいい{animal}'によって「かわいいハムスター」が返され、処理が一時停止します。for文が続く限り、リストの次の要素で同じ処理が繰り返されます。
無限に続くデータストリームの生成
終わりのないデータを生成し続けるジェネレーターです。リアルタイムデータ処理やシミュレーションに使えます。
出力結果
イヌ
ネコ
トリ
イヌ
ネコこの例では、while True:という無限ループの中でリストの要素を順番にyieldしているため、リストの末尾(トリ)の次は先頭(イヌ)に戻り、無限に値が生成されます。
index % len(animals)の部分が、リストのインデックス(0, 1, 2)を繰り返し作り出す処理です。for _ in range(5):のように呼び出し側で回数を指定することで、無限ループするジェネレーターから必要な数だけ値を取り出しています。
複数の条件で絞り込みながらデータを返す処理
特定の条件に合致するデータだけをフィルタリングして返すジェネレーターです。大量のデータから必要なものだけを取り出す処理などに適しています。
出力結果
ゾウは5000kg
ウマは500kgこの例では、リスト内のタプルを一つずつチェックし、size >= min_size(サイズが100以上)という条件を満たす要素だけをyieldで返すため、条件にあう「ゾウ」と「ウマ」のデータだけが出力されます。
for name, size in animals:でリスト内のタプルを順番に取り出し、if文で条件判定を行います。「ネズミ」(1kg)は条件を満たさないため、yieldが実行されず、次のループに進みます。
大量データのバッチ処理
大きなデータセットを小さな塊に分割して処理するパターンです。機械学習のデータ処理などに応用できます。
出力結果
['イヌ', 'ネコ']
['ウサギ', 'ハムスター']
['トリ']この例では、batch_animals関数がbatch_size=2(2個ずつ)でリストを分割し、yieldで返すため、2個、2個、1個のリストが順番に出力されます。
for i in range(0, len(animal_list), batch_size):の部分が、リストのインデックスをbatch_size(2)ずつずらしながらループする処理です(iは0, 2, 4となります)。yield animal_list[i:i + batch_size]で、リストのスライス機能を使って指定した範囲の要素を新しいリストとして返しています。
まとめ
Pythonのyieldは、メモリ効率を重視したプログラミングで役立つ機能です。この記事では、基本的な使い方から実用例まで解説しました。
yieldは、通常のreturn文とは異なり、関数の状態を保持しながら値を1つずつ返せるため、大量のデータを扱う場面に適しています。ジェネレーター関数を活用すると、リスト全体をメモリに用意することなく、必要なタイミングで必要な分だけデータを生成できます。
yieldが活躍する場面
- 巨大なファイルを1行ずつ読み込むとき
- 終わりのない連続データ(数列など)を作るとき
- 大量のデータを加工しながら扱いたいとき
yieldを用いる上で、押さえておきたいポイントを覚えておきましょう。
重要なポイント
- yield文を使うと関数はジェネレーター関数になる
- 呼び出されるたびに値を1個ずつ生成する
- for文やnext()関数で値を取り出す
初めてPythonを学ぶ方も、この記事で紹介したyieldを実際に書いて、基本的な使い方を試してみてください。
大量のデータを効率良く扱う処理は、実際の開発でよく必要とされます。マスターしておけば役立つこと間違いありません。
ぜひyieldをマスターして、よりメモリ効率の良い実用的なプログラムを作成できるようになりましょう。