Python のジェネレータ式

ジェネレータ式は、リスト内包表記に似た構文で簡潔にジェネレータを作成できる方法です。括弧の種類が異なるだけで、メモリ効率に大きな違いがあります。

基本構文

リスト内包表記の角括弧 [] を丸括弧 () に変えるだけで、ジェネレータ式になります。

# リスト内包表記(すべてをメモリに展開)
squares_list = [x ** 2 for x in range(5)]

# ジェネレータ式(遅延評価)
squares_gen = (x ** 2 for x in range(5))

print(type(squares_list))  # <class 'list'>
print(type(squares_gen))   # <class 'generator'>

リスト内包表記との違い

リスト内包表記 [...]

すべての要素を一度に生成してメモリに保持する。要素に何度でもアクセスできる。

ジェネレータ式 (...)

要素を1つずつ遅延生成する。一度しかイテレートできない。メモリ効率が良い。

使い方

ジェネレータ式は for 文や next() で値を取り出します。

gen = (x * 10 for x in range(3))

print(next(gen))  # 0
print(next(gen))  # 10
print(next(gen))  # 20

リストに変換することもできます。

gen = (x ** 2 for x in range(5))
result = list(gen)
print(result)  # [0, 1, 4, 9, 16]

関数の引数として使う

ジェネレータ式を関数の引数に直接渡す場合、外側の括弧を省略できます。

# sum() の引数にジェネレータ式を渡す
total = sum(x ** 2 for x in range(10))
print(total)  # 285

# 括弧を省略しない書き方
total = sum((x ** 2 for x in range(10)))

sum(), max(), min(), any(), all() などと組み合わせると便利です。

条件付きジェネレータ式

if を使ってフィルタリングできます。

# 偶数の二乗だけを生成
even_squares = (x ** 2 for x in range(10) if x % 2 == 0)
print(list(even_squares))  # [0, 4, 16, 36, 64]

メモリ効率の比較

大量のデータを扱う場合、ジェネレータ式は圧倒的にメモリ効率が良いです。

import sys

# 100万要素のリスト
list_comp = [x for x in range(1_000_000)]
print(sys.getsizeof(list_comp))  # 約 8.5MB

# ジェネレータ式
gen_exp = (x for x in range(1_000_000))
print(sys.getsizeof(gen_exp))  # 約 200バイト

ジェネレータ式自体はデータを保持せず、計算方法だけを覚えているため、サイズがほぼ一定です。一度しかイテレートしない処理には、ジェネレータ式を使うことをおすすめします。