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