Python | OOP:yield

Python Python
スポンサーリンク

概要(yieldは「一つ返して一時停止、次に再開」する魔法のスイッチ)

yieldは、関数を“ジェネレーター”に変えるキーワードです。yieldに到達すると値を一つ返して処理を一時停止し、次に呼ばれたときはその直後から続きが動きます。全データを一気に作らず「必要になった分だけ」順に生成するため、メモリ効率が高く、巨大データ・ファイル読取り・ページングAPIと最高の相性です。


基本構文(yieldで作るジェネレーター関数)

もっとも小さな例(順に値を返す)

def count_up(n: int):
    for i in range(n):
        yield i  # 一つ返す → 停止 → 次に再開

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

yieldは「値を返して止まる」合図です。関数は終了せず、次のnext()で前回のyield直後から再開します。forは内部でnext()を繰り返し、尽きたら自動で止まります。

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

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

リスト内包に似ていますが、結果をリスト化せず「必要時に一つずつ」作ります。大きなデータでもメモリが膨らみません。


重要ポイントの深掘り(returnとの違い・遅延生成・終了)

returnとの違い(一括 vs 逐次)

  • returnはその場で関数を終わらせ、結果を一括で返します。
  • yieldは一つ返したら“停止”し、次回は続きから再開します。返すたびに動くので「逐次生成」になります。

遅延生成(必要なときだけ作る)

yieldは値が要求されたタイミングで作るため、100万件でも「今必要な1件」しかメモリに載せません。ストリームやページングのような“終わりが遠い”処理を安全に扱えます。

終了はStopIteration(自然に終わる)

ジェネレーター関数はループを抜ける、もしくはreturnすると自動で終了し、next()はStopIterationを受け取って止まります。明示的にStopIterationを投げる必要は通常ありません。


実践での使い方(ファイル・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 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の間に重いI/Oや複雑な副作用を入れすぎると、1ステップの遅延や失敗が読めなくなります。読み取り・軽い整形はジェネレーター、書き込みや接続は明示メソッドへ分離すると安全です。

速度とピークメモリをバランスする

  • まとめ取得+逐次yield(“バッチ化”)でAPIやDBの往復を減らし、ピークメモリを一定に保ちます。
def batched(fetch_fn, batch_size: int = 100):
    offset = 0
    while True:
        batch = fetch_fn(offset, batch_size)
        if not batch:
            return
        for item in batch:
            yield item
        offset += len(batch)
Python

再利用性(いつでももう一度回せる形)

関数ジェネレーターは毎回“新しい”イテレータが返るため、何度でも回せます。自作クラスに反復を持たせたいときは、iter内でyieldを使うと同じ利点が得られます。


応用(yield from・無限列・制御メソッド)

yield fromで委譲を簡潔に

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

ネストを平らにして、可読性を上げます。

無限ジェネレーターの安全運用

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

for i in naturals():
    if i > 100:
        break  # 呼び手側で境界を付ける
Python

“終わらない”ことは有用ですが、呼び手で上限やbreak条件を必ず設けます。

制御メソッド(必要になったら)

  • g.close(): 早期終了
  • g.send(value): 次のyieldへ値を注入
  • g.throw(exc): ジェネレーター内に例外を投げ込む まずは基本のfor/nextで十分。高度な制御は必要になってから。

例題(ログを安全に要約して流すジェネレーター)

まとめて読み、短く整えて逐次処理

def summarized_logs(read_fn, batch_size: int = 1000):
    """
    read_fn(offset, batch_size) -> list[str]
    を想定。長い行は先頭100文字に短縮して流す。
    """
    offset = 0
    while True:
        batch = read_fn(offset, batch_size)
        if not batch:
            return
        for line in batch:
            s = line.strip()
            yield s[:100] if len(s) > 100 else s
        offset += len(batch)
Python

ピークメモリを一定に、処理の歩幅を軽く、呼び手側はforで自然に消費できます。


まとめ(yieldは「逐次・遅延・安全」な処理を作る最短ルート)

yieldは、関数を“必要な分だけ値を生む”ジェネレーターに変えるスイッチです。returnの“一括”に対して、yieldは“逐次”で、メモリ効率とスケーラビリティが段違い。巨大ファイル、ページングAPI、段階的変換、無限列まで、落ちずに拡張できる。副作用は小さく、バッチ化やパイプラインで歩幅を整える。この型を体に入れれば、初心者でも「速く、軽く、読みやすい」反復処理が自然に書けるようになります。

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