サイトアイコン アマチュア無線局JS2IIU

【Python】ジェネレータ応用編:無限シーケンス、遅延評価、パイプライン処理

こんにちは、JS2IIUです。

今回は脱初心者を目指して、ジェネレータの活用方法をみていきたいと思います。特に大量のデータを逐次処理する場合には活用できるのではないでしょうか。今回もよろしくお願いします。

1. はじめに

Pythonのジェネレータは、メモリ効率を向上させ、データを遅延評価することで必要な分だけを逐次処理できる強力な機能です。特に、大量のデータを扱う場合や、無限に続くシーケンスを生成する場合に役立ちます。

本記事では、ジェネレータの高度な活用方法として「無限シーケンス」「遅延評価」「パイプライン処理」の3つの概念を詳しく解説し、それぞれの特徴と利点を具体的なコード例とともに紹介します。

2. 無限シーケンスを扱うジェネレータ

2.1 無限シーケンスとは?

通常のリストやタプルなどのデータ構造は有限の要素を持ちますが、ジェネレータを使うことで無限に続くデータ列を効率的に生成できます。例えば、数学的な数列や、一定のパターンに従って無限に増加するデータを扱う場合に便利です。

無限シーケンスを実装することで、必要なデータだけを取得しながらメモリ消費を抑えることが可能になります。

2.2 itertools.count() を使った無限シーケンス

Pythonの標準ライブラリ itertools に含まれる count() 関数を使用すると、指定した開始値から無限に増加する数列を生成できます。

Python
import itertools

# 1 から始まり、1ずつ増える無限シーケンス
for i in itertools.count(1):
    print(i)
    if i >= 10:  # 無限ループを防ぐため、適当な条件で終了させる
        break

このコードでは、 itertools.count(1) によって 1, 2, 3, ... という連続した数値が無限に生成されます。 if i >= 10 の条件がなければ、プログラムは永久に実行され続けます。

2.3 フィボナッチ数列の無限シーケンス

ジェネレータを活用すると、フィボナッチ数列のような特定のルールに従う数列を無限に生成することも可能です。

Python
def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

# 最初の10個のフィボナッチ数を出力
fib = fibonacci()
for _ in range(10):
    print(next(fib))

この fibonacci() 関数は、次のフィボナッチ数を求めるたびに yield を使って値を返します。 next(fib) を呼び出すたびに新しい値が生成され、無限に数列を取得することができます。

3. 遅延評価による効率的なデータ処理

3.1 遅延評価とは?

遅延評価(Lazy Evaluation)は、データが必要になったときにのみ計算を行う手法です。通常のリストは要素がすべてメモリに格納されますが、ジェネレータを使えばデータを逐次的に処理できるため、大量のデータを扱う場合にメモリ使用量を抑えることができます。

3.2 ファイルの逐次読み込み

大きなテキストファイルを扱う際に、 readlines() のようなメソッドを使うと、ファイル全体をメモリに読み込んでしまい、パフォーマンスが低下する可能性があります。ジェネレータを活用することで、1行ずつ逐次的に処理することができます。

Python
def read_large_file(filename):
    with open(filename, "r", encoding="utf-8") as file:
        for line in file:
            yield line.strip()

# 使用例(最初の10行を表示)
for i, line in enumerate(read_large_file("large_file.txt")):
    print(line)
    if i >= 9:
        break

この方法を使うと、必要な行だけをメモリにロードできるため、大容量のファイルを効率的に処理できます。

3.3 条件付き遅延評価

ジェネレータを使うと、データのフィルタリングをメモリ効率良く行うことも可能です。

Python
def filter_even_numbers(numbers):
    for num in numbers:
        if num % 2 == 0:
            yield num

# 1から20までの偶数をフィルタリング
nums = range(1, 21)
filtered_nums = filter_even_numbers(nums)
print(list(filtered_nums))

この filter_even_numbers() は、リスト全体を作成せずに偶数のみを返すジェネレータ関数です。 list(filtered_nums) を呼び出すことで、最終的な偶数リストが取得できます。

4. パイプライン処理の実装

4.1 ジェネレータを活用したデータ処理パイプライン

ジェネレータを活用すると、データを段階的に処理するパイプラインを構築でき、効率的なデータ処理が可能になります。

4.1.1 CSVファイルを処理するパイプライン

以下のコードは、大きなCSVファイルを逐次処理するパイプラインの例です。

Python
import csv

def read_csv(filename):
    with open(filename, "r", encoding="utf-8") as file:
        reader = csv.DictReader(file)
        for row in reader:
            yield row

def filter_high_salary(rows, threshold=50000):
    for row in rows:
        if int(row["salary"]) > threshold:
            yield row

def format_output(rows):
    for row in rows:
        yield f"{row['name']} earns {row['salary']} USD"

# パイプライン処理の実行
pipeline = format_output(filter_high_salary(read_csv("employees.csv")))

for record in pipeline:
    print(record)

このパイプラインでは、

  1. read_csv() がCSVファイルを逐次読み込む。
  2. filter_high_salary() が条件に合致する行だけを抽出。
  3. format_output() が出力形式を整形。

5. まとめ

ジェネレータを活用することで、効率的でメモリ消費の少ないデータ処理が可能になります。ぜひ、実際のプロジェクトに取り入れて、パフォーマンス向上を実感してみてください!

最後に、書籍のPRです。
24年9月に出版された「ハイパーモダンPython-信頼性の高いワークフローを構築するモダンテクニック」、Claudio Jolowicz著、嶋田、鈴木訳。開発環境の構築、プロジェクトの管理、テストに関して実践的な内容でとても参考になる一冊です。ぜひ手に取ってみてください。

最後まで読んでいただきありがとうございます。

モバイルバージョンを終了