Python
 Computer >> コンピューター >  >> プログラミング >> Python

Pythonジェネレーター徹底解説:yieldの基本からメモリ効率化テクニックまで

Pythonにおけるジェネレーターは、PEP 255で導入されて以来、言語の中核を担う重要な機能の一つとなっています。本記事では、ジェネレーターの基本的な概念から、実際のコード例、そして大規模データ処理におけるメモリ効率化の実践テクニックまで、詳しく解説します。

ジェネレーターとは何か

ジェネレーターとは、ループの反復動作を制御するための特別なルーチンです。配列を返す関数に似ていますが、決定的な違いがあります。通常の関数が配列全体を一度に返すのに対し、ジェネレーターは値を1つずつ生成(yield)します。これにより、必要なメモリ量を大幅に抑えることができます。

yield」キーワードを含むPython関数は、すべてジェネレーターと呼ぶことができます。通常のPython関数は最初の行から実行を開始し、return文や例外、または関数の終端に達するまで処理が続きます。その際、関数スコープ内で作成されたローカル変数は破棄され、それ以降アクセスできなくなります。

一方、ジェネレーターの場合はどうでしょうか。yieldキーワードに出会うと、関数の状態がフリーズ(一時停止)され、すべての変数がメモリに保持されます。そして、ジェネレーターが再度呼び出されたときに、中断した箇所から実行が再開されるのです。

ジェネレーターはイテレータと組み合わせて使うこともできますし、「next()」を使って明示的に値を取り出すことも可能です。

ジェネレーターの特徴

  • def キーワードで定義される
  • yield キーワードを使用する
  • 複数の yield を含むことができる
  • 戻り値としてイテレータを返す

例1:イテレータとして使うジェネレーター

まずは、forループで直接利用する最もシンプルな例を見てみましょう。

def generator_thr_iter():
    yield 'xyz'
    yield 246
    yield 40.50

for i in generator_thr_iter():
    print(i)

出力結果

xyz
246
40.5

例2:next()を使ったジェネレーターの操作

次に、__next__()メソッドを使って、値を1つずつ明示的に取り出す方法を確認します。

def generator_thr_iter():
    yield 'xyz'
    yield 246
    yield 40.50

>>> g = generator_thr_iter()
>>> g.__next__()
'xyz'
>>> g.__next__()
246
>>> g.__next__()
40.5
>>> g.__next__()
Traceback (most recent call last):
File "<pyshell#39>", line 1, in <module>
g.__next__()
StopIteration

このように、ジェネレーターはすべての要素を一度に返すのではなく、要求されるたびに1つずつ値を返します。次の値が要求されるまで、ジェネレーター関数は一時停止状態のまま待機しています。また、すべての値を返し終えた後にさらに__next__()を呼び出すと、StopIteration例外が発生することも分かります。

実践例:1からnまでの数値の二乗を出力する

ここで、実践的なシナリオを考えてみましょう。「1からnまでの各数値の二乗を計算したい。ただし、nが非常に大きいため、nまでの数値リストを作成するとシステムメモリ全体を占有してしまう」というケースです。

ジェネレーターを使わない場合の問題点

ジェネレーターを使わない場合、アプローチは次のようになるかもしれません。

>>> n = 200000000000
>>> number_list = range(1, n+1)
>>> for i in number_list:
    print(i*i)

このアプローチでは、膨大な数値リストを扱うことになり、大量のシステムメモリを消費してしまいます。

ジェネレーターを使った改善策

より良いアプローチは、数値リストを一切作成せずに反復処理を行うことです。まさにここでジェネレーターが真価を発揮します。

def num_generator(n):
    num = 1
    while True:
        yield num
        if num == n:
            return
        else:
            num += 1

for i in num_generator(200000000000):
    print(i*i)

コードの動作をステップごとに解説

このコードの内部動作を順を追って見ていきましょう。

① 初回呼び出し:forループが初期化されるときにnum_generatorが呼び出され、n = 200000000000がメモリに格納されます。続いてnum = 1が初期化され、無限ループであるwhile Trueに入ります。

② yieldで一時停止:yield numに遭遇した時点で、whileループがフリーズされ、すべてのローカル変数がメモリに保存されます。num = 1なので、値がforループに返されてiに代入され、1 × 1の出力が行われます。

③ 実行の再開:次の呼び出しでは、以前フリーズした地点から実行が再開されます。num == n(1 == 200000000000)はFalseなので、num += 1が実行されてnum = 2となり、whileループが再度回ります。このプロセスが繰り返されます。

④ 終了条件:最終的にnum200000000000に達すると、その値がyieldされた後、num == nがTrueとなり、return文が実行されます。

ジェネレーターがreturn文を実行するか、例外に遭遇するか、またはジェネレーターの終端に達すると、StopIteration例外が発生し、forループの反復はその時点で自動的に停止します。

まとめ

このようにジェネレーターを活用すれば、巨大なリストを作成してシステムメモリを圧迫することなく、2000億までの数値の二乗を出力することが可能になります。日々のプログラミングにおいても、この考え方を応用すれば、よりメモリ効率の高い、洗練されたプログラムを作成できるでしょう。特に大規模データの逐次処理や、無限系列の扱いなどでジェネレーターは絶大な効果を発揮します。

  1. JavaScriptのジェネレーター関数とは?yieldの仕組みと使い方を実例で解説

    JavaScriptのジェネレーター関数(Generator Function)は、yieldキーワードを使って値を順番に生成できる特別な関数です。通常の関数と異なり、一度値を返してもその内部状態(実行位置や変数など)が保持されるため、次に呼び出されたときには、前回yieldを実行した直後の続きから処理を再開できます。 ジェネレーター関数を呼び出すたびに、呼び出し元へ新しい値が1つずつ返されます。この仕組みにより、大量のデータを一括生成するのではなく、必要なタイミングで少しずつ値を取り出すことが可能になります。 ジェネレーター関数の基本的な特徴 関数宣言時に function* を使用して定

  2. 【初心者向け】Pythonのissuperset()メソッドの使い方をわかりやすく解説

    はじめにこの記事では、Pythonのissuperset()メソッドについて、基本的な仕組みから実際のコード例まで詳しく解説します。issuperset()は、セット(集合)に対して使用できるメソッドで、引数として渡されたセットのすべての要素が、呼び出し元のセットに含まれているかどうかを判定します。呼び出し元のセットBが、引数のセットAのすべての要素を含んでいる場合 → True を返すセットAの要素がすべてBに含まれていない場合 → False を返すつまり、「BがAの上位集合(スーパーセット)であるかどうか」を判定するためのメソッドです。基本構文B.issuperset(A)この式は、Bが