記事一覧へ戻る 本の順番で続きを読む

ジェネレータとイテレータ - 効率的なデータ処理

Python3中級 | 2026/02/18 15:44

ジェネレータとイテレータ

イテレータプロトコル

イテレータは __iter__()__next__() を持つオブジェクトです。

class CountUp:
    def __init__(self, start, end):
        self.current = start
        self.end = end

    def __iter__(self):
        return self

    def __next__(self):
        if self.current >= self.end:
            raise StopIteration
        value = self.current
        self.current += 1
        return value

for n in CountUp(1, 5):
    print(n)  # 1, 2, 3, 4

ジェネレータ関数

yield を使うと、イテレータを簡潔に書けます。

def count_up(start, end):
    current = start
    while current < end:
        yield current
        current += 1

for n in count_up(1, 5):
    print(n)  # 1, 2, 3, 4

# ジェネレータは1つずつ値を生成する(遅延評価)
gen = count_up(1, 1000000)
print(next(gen))  # 1
print(next(gen))  # 2
# メモリに全要素を保持しない

ジェネレータの実用例

大きなファイルの行ごと処理

def read_large_file(file_path):
    with open(file_path, 'r', encoding='utf-8') as f:
        for line in f:
            yield line.strip()

# メモリ効率が良い
for line in read_large_file('huge_log.txt'):
    if 'ERROR' in line:
        print(line)

無限シーケンス

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

# 最初の10個だけ取得
from itertools import islice
fib10 = list(islice(fibonacci(), 10))
print(fib10)  # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

データパイプライン

def read_csv_lines(path):
    with open(path) as f:
        next(f)  # ヘッダースキップ
        for line in f:
            yield line.strip().split(',')

def filter_active(rows):
    for row in rows:
        if row[3] == 'active':
            yield row

def extract_names(rows):
    for row in rows:
        yield row[1]

# パイプラインの組み立て(メモリ効率が良い)
lines = read_csv_lines('users.csv')
active = filter_active(lines)
names = extract_names(active)

for name in names:
    print(name)

ジェネレータ式

# リスト内包表記(メモリに全要素を保持)
squares_list = [x**2 for x in range(1000000)]  # 大量のメモリ

# ジェネレータ式(1つずつ生成)
squares_gen = (x**2 for x in range(1000000))   # メモリ効率が良い

# sum, max, minなどに直接渡せる
total = sum(x**2 for x in range(100))
print(total)  # 328350

yield from

def chain(*iterables):
    for it in iterables:
        yield from it

result = list(chain([1, 2], [3, 4], [5, 6]))
print(result)  # [1, 2, 3, 4, 5, 6]

itertools モジュール

from itertools import chain, product, combinations, groupby, count

# chain: 複数のイテラブルを連結
list(chain('ABC', 'DEF'))  # ['A','B','C','D','E','F']

# product: 直積
list(product('AB', '12'))  # [('A','1'),('A','2'),('B','1'),('B','2')]

# combinations: 組み合わせ
list(combinations('ABCD', 2))  # [('A','B'),('A','C'),...]

# groupby: グループ化
data = [('A', 1), ('A', 2), ('B', 3), ('B', 4)]
for key, group in groupby(data, key=lambda x: x[0]):
    print(key, list(group))

まとめ

  • yield でジェネレータ関数を作る(遅延評価)
  • 大きなデータを扱う際はリストよりジェネレータが効率的
  • ジェネレータ式 () はリスト内包表記 [] のメモリ効率版
  • itertools で高度なイテレータ操作ができる