Python | OOP:ジェネレーター

Python Python
スポンサーリンク

概要(ジェネレーターは「必要な分だけ、順に値を生む」仕組み)

ジェネレーターは、yieldを使って「次の値」を一つずつ返し、呼び出しのたびに前回の続きから再開できる関数(またはオブジェクト)です。全データを一度に作らず“遅延生成”するため、メモリ効率が高く、大量データやストリーム処理、ページングAPIに相性抜群です。forで自然に回せて、尽きれば自動で止まるのも魅力です。


基本構文(ジェネレーター関数とジェネレーター式)

ジェネレーター関数(yieldで一時停止→再開)

def count_up(n: int):
    for i in range(n):
        yield i  # 0, 1, ... n-1 を順に返す

g = count_up(3)
print(next(g))  # 0
print(next(g))  # 1
print(next(g))  # 2
# 次でStopIteration(尽きた合図)
Python

yieldは「一つ返して一時停止」の合図です。次に呼ばれると、yieldの直後から処理が再開されます。returnは終了、yieldは継続前提の“一時停止”だと覚えると理解が速いです。

ジェネレーター式(丸括弧で“逐次生成”)

gen = (x * x for x in range(5))  # 0,1,4,9,16 を逐次生成
print(list(gen))  # [0, 1, 4, 9, 16]
Python

リスト内包表記に似ていますが、結果をリストにせず「一つずつ生成するジェネレーター」を返します。大きなデータでもメモリを圧迫しません。


重要ポイントの深掘り(遅延評価・メモリ効率・終了の扱い)

遅延評価でメモリ節約

ジェネレーターは「必要になった時点」で次の値を作るため、100万件でも一度に保持しません。巨大ファイル、APIページング、ストリーム変換などで“落ちない”実装ができます。

終了は自然(StopIteration)

forは内部でnext()を繰り返し、値が尽きるとStopIterationでループを止めます。ジェネレーター関数では、ループを抜ける(またはreturnする)だけで自動的に終了します。

速度と副作用のバランス

重いI/Oや複雑な副作用をyield間に詰め込み過ぎると、1ステップの体感が悪化します。読み取りや軽い整形はジェネレーターに、書き込み・接続は明示メソッドに分離すると予測可能で安全です。


実践例(ファイル・API・パイプライン)

巨大ファイルを逐次処理する

def non_empty_lines(path: str, encoding: str = "utf-8"):
    with open(path, "r", encoding=encoding) as f:
        for line in f:
            s = line.rstrip("\n")
            if s:
                yield s

# forで自然に処理できる(常に数行しかメモリを使わない)
for line in non_empty_lines("data.txt"):
    print(line)
Python

withでクリーンアップを自動化しつつ、行ごとに必要な分だけ処理します。

ページングAPIを“forで全部”扱う

import requests

def users(base_url: str, timeout: float = 5.0):
    page = 1
    while True:
        r = requests.get(f"{base_url}/users", params={"page": page}, timeout=timeout)
        r.raise_for_status()
        items = r.json().get("items", [])
        if not items:
            return
        for u in items:
            yield {"id": str(u.get("id", "")), "name": u.get("name") or "unknown"}
        page += 1
Python

呼び手はforで全ユーザーを扱え、内部ではページ単位で取得するためメモリ効率が高いです。

段階的な変換(小さくつないで安全に拡張)

def normalized_users(src):
    for u in src:
        yield {"id": u["id"], "name": u["name"].strip()}

# 組み合わせて使う
for u in normalized_users(users("https://api.example.com")):
    print(u)
Python

“受け取ったイテラブルを整えて返す”パイプラインにすると、責務分離が明確で拡張が容易です。


応用トピック(yield from・ジェネレーター制御・無限列)

yield from(委譲でコードを簡潔に)

def flatten(list_of_lists):
    for xs in list_of_lists:
        yield from xs  # 内側のイテラブルをそのまま流す
Python

ネストしたループを平らにし、読みやすく保てます。

制御メソッド(close/send/throw)を知っておく

ジェネレーターは、g.close()で早期終了、g.send(value)で次のyieldへ値を投げる、g.throw(exc)で例外を投げ込むなどの制御が可能です。まずは基本のfor/nextに慣れ、必要になったら段階的に使いましょう。

無限ジェネレーターの扱い

def naturals():
    n = 0
    while True:
        yield n
        n += 1

# 利用側で上限やbreakを必ず設ける
for i in naturals():
    if i > 100:
        break
    print(i)
Python

“終わらない”こと自体は有用ですが、呼び手側で安全な境界を必ず設けます。


例題(バッチ取得+逐次出力で“重い処理を軽く”する)

まとめて取りつつ、1件ずつ流す

def batched(fetch_fn, batch_size: int = 100):
    offset = 0
    while True:
        batch = fetch_fn(offset, batch_size)  # e.g., DBからまとめ取り
        if not batch:
            return
        for item in batch:
            yield item
        offset += len(batch)

# 呼び手はforで逐次処理できる(ピークメモリが一定)
Python

ピークメモリを抑え、処理全体の体感を滑らかにします。


まとめ(ジェネレーターは“遅延生成”でスケールする最強の基本形)

ジェネレーターは、yieldで“必要な分だけ順に値を生む”仕組みです。メモリ効率が高く、終了は自動、forで自然に扱える。巨大ファイル、ページングAPI、段階的変換、無限列まで、スケールと保守性を両立できます。まずはジェネレーター関数と式に慣れ、必要に応じてyield fromや制御メソッドへ広げていく。これを体に入れれば、初心者でも「落ちない・速い・読みやすい」反復処理を自在に設計できます。

タイトルとURLをコピーしました