20.

Python yield 完全ガイド — ジェネレータの仕組みと活用

編集
この記事の要点
  • yield を含む関数は ジェネレータ関数になり、呼び出すと即座に ジェネレータオブジェクトを返す
  • next(gen) で次の yield まで実行 → 値を返して関数の実行を一時停止、再度 next で続きから再開
  • メモリ効率: 無限数列・ファイル行・DB クエリ等を 1 要素ずつ生成するのでリストより圧倒的に省メモリ
  • yield from iterable で別のイテラブルに委譲、内部状態も中継
  • ジェネレータ式 (x*2 for x in range(10)) はリスト内包の括弧違い版、巨大データに有効
  • コルーチンとしての側面: gen.send(value) で yield 式に値を注入できる
  • 非同期ジェネレータ async def + yieldasync for でストリーム処理

yield とは何か

Python の yield は関数をジェネレータ関数に変える文です。通常の関数は呼ぶと最後まで実行して戻り値を返しますが、yield を含む関数は呼んでも本体を実行せず、ジェネレータオブジェクトを返します。next()for ループで反復するたびに、yield 文まで実行 → 値を返して一時停止 → 次の next で続きから再開、を繰り返します。

def counter():
    print("start")
    yield 1
    print("middle")
    yield 2
    print("end")
    yield 3

g = counter()        # 関数本体はまだ実行されない
print(type(g))       # 

print(next(g))       # → start  1
print(next(g))       # → middle 2
print(next(g))       # → end    3
print(next(g))       # → StopIteration 例外

return vs yield

項目returnyield
関数の実行呼出時に即実行呼出時はオブジェクトのみ生成
戻り値1 つの値反復ごとに 1 つずつ無限/有限の値列
関数終了return で完全終了yield 後も停止状態で次の呼出を待つ
状態保持しないローカル変数を保持
メモリ戻り値分1 要素分のみ (省メモリ)

メモリ効率: 無限数列 / 大量データ

# ✗ 1 億要素のリスト → メモリを大量消費
def squares_list(n):
    return [i * i for i in range(n)]

# ✓ ジェネレータ → 1 要素分のメモリのみ
def squares_gen(n):
    for i in range(n):
        yield i * i

# 利用例 (両方とも同じ for で書ける)
for x in squares_gen(100_000_000):
    if x > 10_000:
        break

# 無限数列も書ける (リストでは不可能)
def naturals():
    n = 0
    while True:
        n += 1
        yield n

import itertools
first_10 = list(itertools.islice(naturals(), 10))
print(first_10)  # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

yield from: ジェネレータ委譲

def inner():
    yield 1
    yield 2
    yield 3

def outer():
    yield 0
    yield from inner()    # inner の値を全てそのまま渡す
    yield 4

print(list(outer()))      # [0, 1, 2, 3, 4]

# 同等の冗長な書き方
def outer_long():
    yield 0
    for v in inner():
        yield v
    yield 4

# yield from は値だけでなく send() / throw() / return 値も中継する点で
# 単純な for-yield より強力

ジェネレータ式

# リスト内包 (角括弧) → リストを即座に作る
xs = [x * 2 for x in range(1_000_000)]   # 全要素をメモリに展開

# ジェネレータ式 (丸括弧) → ジェネレータを返す
gen = (x * 2 for x in range(1_000_000))  # 1 要素分のみ
total = sum(gen)                          # 反復しながら計算

# 関数の引数として渡すと括弧省略可能
total = sum(x * 2 for x in range(1_000_000))
max_val = max(len(line) for line in open("data.txt"))

コルーチンとしての yield (send / throw)

yield はでもあります。gen.send(value) でジェネレータに値を送り込めます:

def echo():
    while True:
        received = yield   # ここで止まる、send で値が来る
        print(f"got: {received}")

g = echo()
next(g)            # ジェネレータを yield 位置まで進める (priming)
g.send("hello")    # → got: hello
g.send("world")    # → got: world

# 累積平均コルーチン
def averager():
    total = 0
    count = 0
    avg = None
    while True:
        value = yield avg
        total += value
        count += 1
        avg = total / count

a = averager()
next(a)
print(a.send(10))  # 10.0
print(a.send(20))  # 15.0
print(a.send(30))  # 20.0

この機構は asyncio 登場前 (Python 3.4 以前) の非同期処理の基礎でした。現在は async/await の方が読みやすいため、コルーチン目的では yield をあまり使いません。

非同期ジェネレータ (Python 3.6+)

import asyncio

async def fetch_pages(urls):
    for url in urls:
        # async I/O で取得
        await asyncio.sleep(0.1)
        yield f"page of {url}"

async def main():
    urls = ["a", "b", "c"]
    async for page in fetch_pages(urls):
        print(page)

asyncio.run(main())

他言語の yield との比較

言語構文特徴
Pythonyield value関数全体がジェネレータ化、双方向通信可
C# / Unityyield return value;IEnumerable を返す、コルーチンにも
JavaScriptfunction* gen() { yield value; }function* 構文で明示、Iterator プロトコル
Rubyyield valueブロックへ値を渡す (Python と意味が異なる)
Kotlinyield(value) in sequencesequence ビルダ内のみ

典型的なユースケース

  • ファイル行を 1 行ずつ処理: for line in open("huge.csv") 自体がジェネレータ
  • DB クエリのストリーミング: cursor をジェネレータでラップ
  • ツリー走査: 再帰的に yield from で深さ優先列挙
  • パイプライン処理: gen1 → gen2 → gen3 の連鎖で省メモリな ETL
  • ページネーション: API の next_page を辿ってフラットに列挙

FAQ

Q: ジェネレータは何回でもループできる?
A: 1 回のみ。使い切ったら再度生成し直す必要があります。複数回反復したいなら list 化するか、iterable クラス (__iter__ 実装) にします。

Q: return value をジェネレータ内で使うと?
A: Python 3.3+ では StopIteration(value) を発生させます。yield from で値を受け取れます。

Q: yield と await の関係
A: 内部実装的には親戚 (どちらもコルーチン)。await は asyncio の文脈、yield はジェネレータの文脈、と使い分けます。

Q: ジェネレータをデバッグしにくい
A: 一度 list(gen) で展開してから print するのが基本。または printyield 直前に挟む。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 基本事項
  2. HTMLへの埋め込み
  3. 変数
  4. 可変変数
  5. 定数
  6. データ型
  7. キャスト
  8. エスケープ文字
  9. 配列
  10. 演算子
  11. 代入の際の注意点
  12. 条件分岐
  13. 繰り返し処理
  14. クラスとインスタンス
  15. コンストラクタ
  16. 関数
  17. スーパーグローバル変数
  18. スコープ
  19. staticについて
  20. yieldについて
  21. ファイルのアップロード方法
  22. DB接続方法
  23. SQL実行方法
  24. カプセル化の具体例
  25. 継承の構文
  26. オーバーライド
  27. ポリモーフィズム(多様性)の具体例
  28. 抽象クラス・メソッドの構文と具体例
  29. GET通信
  30. try catchで全てのエラーを拾う方法