この記事のポイント

  • yield / yield fromによるジェネレータ設計の崩壊ポイントをレビューできる
  • 状態管理責務と制御フロー設計の分離技術を理解できる
  • 実務現場で安全に使うためのレビュー観点を整理できる

そもそもyield / yield fromとは

yieldとは

Pythonのyieldは、関数の途中で値を返却しつつ状態を保持する仕組みです。

def count_up(n):
    for i in range(n):
        yield i
  • 状態を持ったまま次回呼び出し可能
  • イテレータ実装が簡潔になる
  • lazy評価による性能向上

yield fromとは

yield fromサブジェネレータへの委譲を表現します。

def nested():
    yield from count_up(3)
  • 複数ジェネレータを合成可能
  • flatten・ラッピング用途に強力

なぜこれをレビューするのか

現場では以下の失敗が多発します。

  • 状態管理が暗黙化して責務肥大
  • 再入可能性が保証されていない
  • yieldと副作用が混在
  • yield from乱用による抽象化破綻
  • エラーハンドリング崩壊

レビューアーは「このジェネレータはどこまで責務を持つべきか?」を冷静に読み取る必要があります。

レビューアー視点

  • ジェネレータ内の責務線引きが明確か
  • yield箇所が安定位置で設計されているか
  • 状態保持責務が混在していないか
  • yield fromの委譲先が適切に抽象化されているか
  • 終了条件・例外設計が整理されているか

開発者視点

  • データ供給責務はジェネレータへ、状態制御責務は外部へ分離
  • yieldは「安定データ発行地点」設計
  • 副作用ロジックを内部混在させない
  • yield fromは構造合成・委譲に限定活用
  • finally・try構造でクリーン終了設計

良い実装例

なぜこの実装が良いのか

  • 状態管理は外部ループへ移譲
  • yield位置は完全に安定
  • yield fromは責務委譲に限定使用
  • エラーハンドリングも外部完結
# data_reader.py

def read_lines(file_path: str):
    with open(file_path) as f:
        for line in f:
            yield line.strip()

def filtered_lines(file_path: str, prefix: str):
    yield from (
        line for line in read_lines(file_path)
        if line.startswith(prefix)
    )
補足

状態制御とデータ供給責務を綺麗に分離できています。レビューアーは副作用混在有無を確認します。

レビュー観点

  • 状態制御とデータ供給が分離されているか
  • yield箇所が安定位置に整理されているか
  • yield fromが委譲責務整理に使われているか
  • finally等によるクリーン終了設計が施されているか
  • 副作用・状態管理がジェネレータ内で暴走していないか

良くない実装例: ケース1(副作用混入による責務混濁)

# bad_side_effects_mixed.py

def fetch_records():
    connection = connect_db()
    cursor = connection.cursor()
    for row in cursor:
        log_access(row)
        yield row
    connection.close()
@Reviewer
外部依存(DB接続・ログ)が混入し、責務肥大化しています。ジェネレータは純粋データ供給責務に分離してください。

問題点

  • DB接続管理責務混入
  • ログ副作用混入
  • エラーハンドリング不能化

改善例

# good_responsibility_split.py

def read_rows(cursor):
    for row in cursor:
        yield row

# DB管理は外部層で責務整理
def process_records():
    with connect_db() as connection:
        cursor = connection.cursor()
        for row in read_rows(cursor):
            log_access(row)

副作用責務は必ず外部層に移譲します。レビューではI/O・DB・ネットワーク混入を即確認します。

良くない実装例: ケース2(終了条件崩壊による無限ループ事故)

# bad_missing_exit.py

def endless_generator():
    n = 0
    while True:
        yield n
        n += 1
@Reviewer
終了条件が設計不明確で無限ループ化しています。停止条件責務を設計段階で整理してください。

問題点

  • 呼び出し側が終了条件責務を背負わされる
  • 運用事故リスク
  • 意図不明設計

改善例

# good_bounded_generator.py

def bounded_generator(limit: int):
    for n in range(limit):
        yield n

終了条件は明示型API設計に組み込むのが原則です。レビューでは無限ループ有無を重点確認します。

良くない実装例: ケース3(yield from乱用による抽象崩壊)

# bad_yield_from_overuse.py

def all_records():
    yield from read_file()
    yield from read_db()
    yield from read_api()
@Reviewer
責務混合ソースが一括委譲されています。個別抽象責務に分割整理してください。

問題点

  • 複数責務混合
  • ソース依存性分離不能
  • 影響範囲肥大

改善例

# good_composable_generators.py

def all_records():
    yield from file_records()
    yield from db_records()
    yield from api_records()

データソース単位で責務抽象化を分離するのが安定設計です。レビューでは委譲粒度を必ず確認します。

良くない実装例: ケース4(finally未使用によるリーク)

# bad_no_finally_cleanup.py

def file_reader(path: str):
    f = open(path)
    for line in f:
        yield line
    f.close()
@Reviewer
finally未使用で例外発生時のclose漏れリスクがあります。withまたはtry-finally適用を検討してください。

問題点

  • 中断時close未実行可能性
  • リソースリーク発生
  • 運用事故誘発

改善例

# good_safe_resource_management.py

def file_reader(path: str):
    with open(path) as f:
        for line in f:
            yield line

リソース開放責務は常にfinally系構文で管理が原則です。レビューでは漏洩リスクを優先確認します。

観点チェックリスト

まとめ

ジェネレータ設計レビューは「状態制御・責務分離・安全終了設計」の総合訓練です。
レビューアーは常に

  • このyieldは何責務?
  • 状態保持は誰の責任?
  • 終了安全性は保証されている?

を読み取り、「ストリーム処理の責務線引き」を鍛えていく必要があります。
yield / yield from設計レビューは現場育成教材として極めて強力です。