Python のジェネレータ関数と yield

ジェネレータ関数は yield を使って値を逐次生成します。大量のデータをメモリに載せずに処理できるため、効率的なイテレーション処理が可能です。

基本構文

def count_up(n):
    for i in range(n):
        yield i

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

yield に到達すると値を返して一時停止し、次の反復で再開します。

リストとの違い

# リスト:全要素をメモリに保持
def get_list(n):
    return [i for i in range(n)]

# ジェネレータ:必要な時に生成
def get_generator(n):
    for i in range(n):
        yield i

100 万件のデータを処理する場合、リストはすべてをメモリに載せますが、ジェネレータは 1 件ずつ処理します。

ジェネレータ式

リスト内包表記と同様の構文で、丸括弧を使います。

gen = (x ** 2 for x in range(10))
print(next(gen))  # 0
print(next(gen))  # 1

yield from

別のイテラブルから値を委譲できます。

def flatten(nested):
    for item in nested:
        if isinstance(item, list):
            yield from flatten(item)
        else:
            yield item

data = [1, [2, 3, [4, 5]], 6]
print(list(flatten(data)))  # [1, 2, 3, 4, 5, 6]

send と双方向通信

ジェネレータに値を送り込むこともできます。

def accumulator():
    total = 0
    while True:
        value = yield total
        if value is not None:
            total += value

gen = accumulator()
next(gen)         # ジェネレータを起動
print(gen.send(10))  # 10
print(gen.send(20))  # 30

ジェネレータはメモリ効率の良いデータ処理、無限シーケンス、パイプライン処理などに活用できます。