#35. 発電機

ジェネレーターは再開可能な関数です。通常の関数は、開始、実行、および 1 つの戻り値で終了します。ジェネレーターは、開始して値を生成し、フレームを一時停止し、後で同じ命令位置から再開して別の値を生成し、終了するまで繰り返すことができます。

ジェネレーター関数は、以下を含む関数本体です。yieldpython id="j7t4va" def numbers(): yield 1 yield 2 yield 3 この関数を呼び出しても、本体はすぐには実行されません。python id="oyb9ik" g = numbers() この呼び出しによりジェネレーター オブジェクトが作成されます。ジェネレーターが再開されると本体が開始されます。python id="9sgmxp" print(next(g)) print(next(g)) print(next(g)) 出力:text id="wj3i3f" 1 2 3 最後の値の後、次の再開で値が発生しますStopIteration

35.1 ジェネレータ関数とジェネレータ オブジェクト

ジェネレーター関数は、次のように定義された呼び出し可能関数です。def

ジェネレーター オブジェクトは、ジェネレーター関数が呼び出されたときに返される再開可能なイテレーターです。```python id="nckwxg" def gen(): yield 1

print(gen) print(gen()) 概念的には:text id="hfh6hz" gen function object

gen() generator object suspended execution state code object frame or frame-like state これは通常の関数とは異なります。python id="wsroz3" def f(): return 1

f() ```電話をかけるfすぐに本体を実行して戻ります1

電話をかけるgen後で本体を実行できるオブジェクトを返します。

##35.2yieldyield式はジェネレーターの呼び出し元に値を生成し、実行を一時停止します。python id="cwhj6e" def gen(): x = 10 yield x x = 20 yield x 実行シーケンス:```text id="jvg4tx" create generator object resume generator x = 10 yield 10 suspend

resume generator x = 20 yield 20 suspend

resume generator finish function raise StopIteration ```ローカル変数xジェネレータは実行状態を維持するため、一時停止後も存続します。

35.3 ジェネレータはイテレータである

ジェネレーター オブジェクトはイテレーター プロトコルを実装します。```python id="mtd1nu" g = numbers()

print(iter(g) is g) print(next(g)) ジェネレータには次の機能があります。text id="vlpm43" iter next send throw close の`for`ジェネレーターはイテレーターであるため、ループは機能します。python id="9z52dp" for x in numbers(): print(x) 概念的には:text id="rdvvmh" it = iter(numbers())

while True: try: x = next(it) except StopIteration: break print(x)


ジェネレーターは再開間で実行状態を保存する必要があります。

その状態には次のものが含まれます。```text id="9ta9ii"
code object
instruction position
local variables
value stack
exception state
closure cells
running state
finished state
```例:```python id="h463cg"
def gen():
    a = 1
    b = 2
    yield a
    yield b
```最初のあと`yield`、ジェネレーターは次のことを覚えておく必要があります。```text id="oj7swc"
a = 1
b = 2
next instruction is after first yield
```これが、ジェネレーターがフレームにしっかりと接続されている理由です。

## 35.5 通常の関数フレームとジェネレーターフレーム

通常、通常の関数フレームは復帰後に消滅します。```python id="lzuq79"
def f():
    x = 1
    return x
````f()`戻るとフレームをクリアできます。

ジェネレータ フレームは一時停止中も存続します。```python id="6zyz25"
def gen():
    x = 1
    yield x
````next(gen())`到達する`yield`、後で再開される可能性があるため、フレームをクリアできません。

|特集 |通常の機能 |ジェネレーター |
|---|---|---|
|呼び出しは本体をすぐに実行します。はい |いいえ |
|一時停止できます |いいえ |はい |
|譲歩した後も地元民を維持する |いいえ |はい |
| 1 つの最終結果を返します |はい |最終結果は次のようになります`StopIteration.value`|
|イテレータプロトコルを実装します。いいえ |はい |
|フレームの寿命 |通常の通話時間 |完了または終了するまで |

##35.6`next()`

`next(g)`発電機を再開します。```python id="07krhu"
def gen():
    yield "a"
    yield "b"

g = gen()

print(next(g))
print(next(g))
```実行:```text id="tuypws"
next(g)
    resume at start
    yield "a"
    return "a" to caller

next(g)
    resume after first yield
    yield "b"
    return "b" to caller
```ジェネレーター オブジェクトは、呼び出し間の命令の位置を記録します。

## 35.7 完了と`StopIteration`ジェネレータが終了すると、発生します`StopIteration`。```python id="suekt2"
def gen():
    yield 1

g = gen()
print(next(g))
print(next(g))
```2番目`next(g)`上げる`StopIteration`

ジェネレーターは次のようにして終了できます。```text id="6n5ndu"
falling off the end
executing return
raising an exception
being closed
```端から落ちることは戻ることと同じです`None````python id="51z1x1"
def gen():
    yield 1
````yield 1`、関数は最後に到達します。次の履歴書が上がります`StopIteration`

##35.8`return`発電機の中で

発電機で使用できるのは、`return value````python id="lbrk4s"
def gen():
    yield 1
    return 99
```戻り値は次のようになります。`value`の属性`StopIteration````python id="tyi2fu"
g = gen()

print(next(g))

try:
    next(g)
except StopIteration as exc:
    print(exc.value)
```出力:```text id="dzo3b2"
1
99
````for`ループは最後の部分を無視します`StopIteration.value`

##35.9`yield`式です`yield`を通じて値を受け取ることができます`send`。```python id="vpynq0"
def gen():
    x = yield "ready"
    yield x
```使用:```python id="5cofsb"
g = gen()

print(next(g))
print(g.send(42))
```実行:```text id="fafbmz"
next(g)
    runs until yield "ready"
    returns "ready"

g.send(42)
    resumes generator
    yield expression evaluates to 42
    x = 42
    yield x
```それで`yield`どちらも値を送信し、値を受信できます。

## 35.10 ジェネレータの起動`send`新しく作成されたジェネレーターが最初に到達していません`yield`。

したがって、最初の履歴書では次のようにする必要があります。`next(g)`または`g.send(None)````python id="ltjgnn"
g = gen()
g.send(None)
````None`開始したばかりのジェネレーターへの値は、中断されていないためエラーになります。`yield`それを受け取る表現。```python id="w4yvh2"
g = gen()
g.send(42)
```これにより、`TypeError`

##35.11`throw`

`throw`中断された時点で例外を発生させてジェネレーターを再開します。`yield````python id="1i4m5p"
def gen():
    try:
        yield "ready"
    except ValueError:
        yield "handled"

g = gen()

print(next(g))
print(g.throw(ValueError))
```実行:```text id="h3sq71"
next(g)
    yield "ready"

g.throw(ValueError)
    resume at yield by raising ValueError
    except ValueError catches it
    yield "handled"

throw呼び出し元がジェネレーターに例外を挿入できるようにします。

35.12close

closeジェネレーターに終了を要求します。```python id="4p6xj9" def gen(): try: yield 1 finally: print("cleanup")

g = gen() next(g) g.close() ```注入の終了GeneratorExit発電機の中へ。のfinallyブロックが実行されます。

ジェネレーターは閉じている間は通常の値を生成すべきではありません。存在する場合、CPython はエラーを発生させます。RuntimeErrorpython id="m1acqp" def bad(): try: yield 1 finally: yield 2 電話をかけるclose()最初の降伏後は、ジェネレーターがクローズ中に降伏したため、エラーが発生します。

35.13 ジェネレータの状態

ジェネレーターはいくつかの状態をとることができます。text id="spbcno" created running suspended closed 使用するinspect:

import inspect

def gen():
    yield 1

g = gen()
print(inspect.getgeneratorstate(g))
next(g)
print(inspect.getgeneratorstate(g))
try:
    next(g)
except StopIteration:
    pass
print(inspect.getgeneratorstate(g))
```典型的な状態:```text id="51wa06"
GEN_CREATED
GEN_SUSPENDED
GEN_CLOSED
```発電機がすでに動作している間は再開できません

## 35.14 再入保護

発電機に再投入することはできません。```python id="h0ak06"
def gen():
    yield next(g)

g = gen()
next(g)
```これにより再開が試みられます`g`その間`g`はすでに実行中です CPython ではエラーが発生します

ジェネレーターにはフレーム状態の破損を防ぐための実行フラグがあります

概念的には:```text id="lm6k96"
if generator is already executing:
    raise ValueError or RuntimeError depending on context
```これにより吊り下げられたフレームとスタックが保護されます

## 35.15 ジェネレーターのバイトコード

ジェネレーター関数はジェネレーターとしてマークされたコード オブジェクトにコンパイルされます。```python id="af0twx"
def gen():
    yield 1
```この関数を呼び出すとフレームを最後まで実行するのではなくジェネレーター オブジェクトが作成されます

概念的な命令シーケンス:```text id="n6w1a4"
LOAD_CONST 1
YIELD_VALUE
RESUME
LOAD_CONST None
RETURN_VALUE
```正確なバイトコードは Python のバージョンによって異なります

重要な指示は、`YIELD_VALUE`、呼び出し元に値を送信し実行を一時停止します

##35.16`yield from`

`yield from`別のイテレータまたはジェネレータにデリゲートします。```python id="uxya55"
def outer():
    yield from inner()
```これは次とほぼ同等です。```python id="37kb2t"
for value in inner():
    yield value
```しかしそれはまた次のようにも転送します。```text id="hq74n9"
send
throw
close
StopIteration.value
```これにより、`yield from`単純なループよりも強力です

## 35.17 委任と`yield from`例:```python id="9ui4p5"
def inner():
    yield 1
    yield 2
    return 99

def outer():
    result = yield from inner()
    yield result

print(list(outer()))
```出力:```text id="4ncupl"
[1, 2, 99]
```の戻り値`inner`の結果になります`yield from`での表現`outer`。

概念的には:```text id="02qghi"
outer delegates to inner
inner yields 1
outer yields 1 to caller

inner yields 2
outer yields 2 to caller

inner returns 99 via StopIteration.value
yield from expression evaluates to 99
outer yields 99
```##35.18`yield from`そして`send`

`yield from`呼び出し元から送信された値を転送します。```python id="nfxupw"
def inner():
    x = yield "inner ready"
    yield x

def outer():
    yield from inner()

g = outer()
print(next(g))
print(g.send(42))
```出力:```text id="9lagzv"
inner ready
42
````send(42)`一時停止に達する`yield`内部`inner`。

##35.19`yield from`と例外`yield from`例外も転送します。```python id="nxzr78"
def inner():
    try:
        yield "ready"
    except ValueError:
        yield "handled"

def outer():
    yield from inner()

g = outer()
print(next(g))
print(g.throw(ValueError))
```例外がスローされるのは、`inner`では直接取り扱っておりません。`outer`ただし委任が終了するか内部に適切なハンドラーが存在しない場合を除きます

## 35.20 ジェネレータ式

ジェネレーター式はジェネレーターのようなオブジェクトを作成します。```python id="gkiij5"
squares = (x * x for x in range(10))
```怠け者です値は要求に従って計算されます。```python id="ey0d2q"
print(next(squares))
print(next(squares))
```ジェネレーター式には独自の暗黙的な関数のようなスコープがあります。```python id="wkic1b"
x = 100
g = (x for x in range(3))
print(x)
```外側`x`残っている`100`。

## 35.21 リスト内包表記とジェネレーター式

リスト内包表記によりリスト全体が即座に構築されます。```python id="h6kqv5"
xs = [x * x for x in range(10)]
```ジェネレーター式は値を遅延して生成します。```python id="j8cabg"
g = (x * x for x in range(10))
```|特集 |リスト内包表記 |ジェネレータ式 |
|---|---|---|
|評価 |熱心に |怠け者 |
|結果 |リスト |ジェネレータのようなイテレータ |
|メモリ |すべての結果を保存します |実行状態を保存します |
|再利用可能 |はいリストは何度も反復できますいいえジェネレーターが消費されています |
|範囲 |自分の理解範囲 |独自のジェネレータ スコープ |

## 35.22 ワンショット反復

ジェネレーターはワンショット反復子です。```python id="mj534b"
g = (x for x in range(3))

print(list(g))
print(list(g))
```出力:```text id="qbh3cb"
[0, 1, 2]
[]
```発電機は一度使い果たされると使い果たしたままになります

これはリストなどのコンテナとは異なります。```python id="b4xrxl"
xs = [0, 1, 2]
print(list(xs))
print(list(xs))
```リストは毎回新しい反復子を作成しますジェネレーターはそれ自体のイテレーターです

## 35.23 遅延評価

ジェネレーターはオンデマンドで値を計算します。```python id="c1xawr"
def read_lines(path):
    with open(path) as f:
        for line in f:
            yield line.rstrip("\n")
```これはファイル全体をメモリに読み込むわけではありません一度に 1 行ずつ読み取って出力します

遅延実行は次の場合に役立ちます。```text id="dhu35c"
large files
streams
pipelines
infinite sequences
expensive computations
early stopping
```:```python id="zb1yck"
def count():
    n = 0
    while True:
        yield n
        n += 1
```このジェネレータは無限シーケンスを表します

## 35.24 パイプラインのスタイル

ジェネレーターは自然に作曲します。```python id="j6opqr"
def numbers(path):
    with open(path) as f:
        for line in f:
            yield int(line)

def positive(xs):
    for x in xs:
        if x > 0:
            yield x

def squared(xs):
    for x in xs:
        yield x * x
```使用:```python id="1rj6pt"
pipeline = squared(positive(numbers("data.txt")))

for x in pipeline:
    print(x)
```各ステージは前のステージから引き継がれます完全な中間リストは必要ありません

## 35.25 ジェネレーターのクリーンアップ

リソースを管理するジェネレーターは使用する必要があります`try/finally`またはコンテキストマネージャー。```python id="8m79yy"
def lines(path):
    f = open(path)
    try:
        for line in f:
            yield line
    finally:
        f.close()
```発電機が使い果たされる前に閉じられると、`finally`ブロックが実行されます

より良い形式:```python id="yyr3qh"
def lines(path):
    with open(path) as f:
        for line in f:
            yield line
````with`ステートメントはジェネレーターを閉じるときに機能するクリーンアップ ロジックにコンパイルされます

## 35.26 ジェネレーターとリソースリーク

中断されたジェネレーターはリソースを存続させ続ける可能性があります。```python id="2s3f45"
def gen():
    f = open("data.txt")
    yield f.readline()
    f.close()
```呼び出し元が最初の値の後で停止しジェネレーターを閉じない場合ジェネレーターが収集されるまでファイルは開いたままになる可能性があります

使用`with`または明示的に閉じます。```python id="lz764o"
g = gen()
next(g)
g.close()
```リソースの所有権はジェネレーター コードで明示的に指定する必要があります

## 35.27 ジェネレーターと例外`finally`クリーンアップ コードが発生すると、その例外はジェネレーターのクローズ中または終了中に伝播します。```python id="xdp983"
def gen():
    try:
        yield 1
    finally:
        raise RuntimeError("cleanup failed")
```電話をかける`g.close()`発電機を始動すると上昇します`RuntimeError`。

通常の呼び出し元コンテキストがない場合終了処理時の例外は発生不能として報告される場合があります

## 35.28 ジェネレータのメモリ保持

中断されたジェネレーターはローカル変数を維持します。```python id="7evyaw"
def gen():
    data = bytearray(100_000_000)
    yield 1
    return len(data)

g = gen()
next(g)
```最初のあと`yield`、`data`ジェネレーターが再開して使用できるため生きたままになります

保持チェーン:```text id="a05d71"
generator object
    suspended frame
        local data
```メモリを解放するにはジェネレータを使い果たすか閉じるか複数のイールドにわたって大きなローカルを保持しないようにします

## 35.29 大規模なローカルのクリアリング

譲歩後に大きなオブジェクトが必要ない場合は譲歩する前または長時間停止する前にそれを片付けてください。```python id="mdphyz"
def gen():
    data = bytearray(100_000_000)
    result = process(data)
    data = None
    yield result
```これにより大きな物体を吊り下げる前に解放することができます

ジェネレーター フレームはまだ存在しますが参照されなくなりました。`data`。

## 35.30 ジェネレーターと`for`ループ

発電機は内部に現れることがよくあります。`for`ループ:```python id="ywz49k"
for value in gen():
    use(value)
```ループは繰り返し呼び出します`next()`それまで`StopIteration`。

ループが早期に終了した場合、`break`、ジェネレータ オブジェクトが到達不能になり後で閉じる可能性がありますただし即時ファイナライズに依存するかどうかは実装によって異なりますクリーンアップのタイミングが重要な場合はコンテキスト マネージャーを使用します

## 35.31 ジェネレーターベースのコンテキストマネージャー

`contextlib.contextmanager`デコレータはジェネレータをコンテキスト マネージャに変えます。```python id="vtcps7"
from contextlib import contextmanager

@contextmanager
def managed():
    print("enter")
    try:
        yield "value"
    finally:
        print("exit")

with managed() as value:
    print(value)
```ジェネレーターは 1 回だけ出力します

概念的には:```text id="2vq7qx"
__enter__
    run generator until yield
    return yielded value

__exit__
    resume generator to run cleanup
```with-body が発生すると例外がジェネレータの次の位置でスローされます。`yield`。

## 35.32 ジェネレータープロトコルメソッド

ジェネレーター オブジェクトは次の重要なメソッドをサポートしています

|方法 |意味 |
|---|---|
|`__next__()`|再開して送信`None` |
| `send(value)`|再開して値を現在の値に送信します`yield` |
| `throw(exc)`|現在の状態で例外を発生させて再開する`yield` |
| `close()`|注入する`GeneratorExit`そして閉じます |
|`__iter__()`|自分自身を返す |`next(g)`電話`g.__next__()`。`g.__next__()`と同等です`g.send(None)`吊り下げられた発電機の場合

## 35.33 ジェネレータの属性

ジェネレーター オブジェクトは便利な属性を公開します。```python id="yu9p0l"
def gen():
    yield 1

g = gen()

print(g.gi_code)
print(g.gi_frame)
print(g.gi_running)
```共通の属性には次のものがあります

|属性 |意味 |
|---|---|
|`gi_code`|コードオブジェクト |
|`gi_frame`|フレームまたは`None`閉じたとき |
|`gi_running`|現在実行中かどうか |
|`gi_yieldfrom`|現在の委任イテレータ`yield from`、あれば |

これらは CPython レベルのイントロスペクション フックであり実装の詳細が公開される可能性があります

## 35.34 ジェネレーターとトレースバック

ジェネレーターが例外を発生させた場合トレースバックにはジェネレーター フレームが含まれます。```python id="u2d2xz"
def gen():
    yield 1
    1 / 0

g = gen()
next(g)
next(g)
```2番目`next(g)`ジェネレーター内で再開し上昇します`ZeroDivisionError`。

トレースバックは内部の失敗した行を指しています`gen`。

ジェネレーター フレームは他の Python フレームと同様にトレースバックの一部です

## 35.35 ジェネレーターと`StopIteration`変身

発電機の中偶然`StopIteration`危険です。```python id="mmdpxy"
def gen():
    next(iter([]))
    yield 1
```内部`next(iter([]))`上げる`StopIteration`。

最新の Python はこれを次のように変換します。`RuntimeError`発電機本体から漏れたときこれによりジェネレータの正常な完了のように見える偶発的な終了が防止されます

正しいコードは予期される場合にはそれを明示的にキャッチする必要があります。```python id="tmjsqq"
def gen():
    try:
        value = next(iter([]))
    except StopIteration:
        return
    yield value
```## 35.36 ジェネレーターと非同期

ジェネレーターはコルーチンと非同期ジェネレーターに関連していますがそれらとは異なります

|構築 |キーワード |プロデュース |
|---|---|---|
|ジェネレーター |`def``yield`|ジェネレーターオブジェクト |
|コルーチン |`async def`|コルーチン オブジェクト |
|非同期ジェネレーター |`async def``yield`|非同期ジェネレータ オブジェクト |

通常の発電機は、`next`、`send`、`throw`、 そして`close`。

コルーチンは使用します`await`そしてイベントループのスケジューリング

非同期ジェネレーターは使用します`async for`そして`anext`。

これらは実行を再開できるという考えを共有していますがプロトコルが異なります

## 35.37 CPython 実行モデル

CPython レベルではジェネレーターは中断された実行状態を所有するヒープ オブジェクトです

概念的には:```text id="y0j37u"
PyGenObject
    code object
    frame or interpreter frame state
    name and qualname
    exception state
    running flag
    weakrefs
    yield-from target
```正確な構造はバージョンによって異なりますが概念的なフィールドは残ります

再開時:```text id="zrin3w"
check generator is not closed
check generator is not already running
mark running
enter evaluation loop with saved frame
run until yield, return, or exception
save frame state if yielded
clear frame state if completed
mark not running
return yielded value or propagate exception
```## 35.38`YIELD_VALUE`の`YIELD_VALUE`命令はキーのバイトコード操作です。

概念的には:```text id="bpj8tu"
value = pop stack
save current frame position
return value to generator caller
mark generator suspended
```再開するとyield 命令の後も実行が継続されます

返される値は関数の最終的な戻り値ではありませんこれはイテレータ プロトコルによって配信される中間結果です

## 35.39`SEND`と代表団

最新のバイトコードはジェネレーターコルーチン委任パスに値を送信するための特別なサポートを備えています

概念的には送信操作は次のようになります。```text id="z95s95"
resume suspended iterator/coroutine
send value or None
receive yielded value, return value, or exception

yield fromそしてawaitどちらも、別の再開可能なオブジェクトへの送信に依存します。

これは、完全なループを手動で作成することなく、ネストされた再開可能な計算が接続される方法です。

35.40 よくある誤解

誤解 正しいモデル
ジェネレーター関数を呼び出すと実行されます。ジェネレーター オブジェクトを作成します。
yieldと同じですreturn yield一時停止します。return完了
発電機は使い果たした後も再利用可能 ワンショットです
ジェネレーターはすべての値を保存します。実行状態を保存し、遅延計算します。
yield fromはループの構文にすぎません また、send、throw、close、戻り値も転送します。
ジェネレーターは、各降伏後にローカルを解放します。停止中も地元住民は生き残る
forループが見るStopIteration.value
close()単なる削除です 注入するGeneratorExitそしてクリーンアップを実行します

35.41 読書戦略

小さなジェネレーターから始めます。python id="wby2x2" def gen(): x = 1 yield x x = 2 yield x 検査:```python id="hvaeen" import dis import inspect

g = gen()

print(inspect.getgeneratorstate(g)) dis.dis(gen)

print(next(g)) print(inspect.getgeneratorstate(g)) print(g.gi_frame.f_locals)

print(next(g)) print(inspect.getgeneratorstate(g)) print(g.gi_frame.f_locals) 次に、次のことを勉強します。text id="tf7v1e" return value send throw close yield from try/finally generator expressions contextlib.contextmanager ケースごとに、以下を追跡します。text id="a4iald" when the body starts where execution suspends which locals remain alive what resumes execution what exception or value crosses the boundary when the frame is cleared


ジェネレーターは、実行状態が保存された反復子オブジェクトとして実装された再開可能な関数です。ジェネレーター関数呼び出しにより、ジェネレーター オブジェクトが作成されます。本体は、発電機が再開された場合にのみ動作します。`next``send``throw` または`close`

コアモデルは次のとおりです。```text id="4a89xx"
generator function call
    
create generator object with suspended frame
    
next/send resumes frame
    
yield returns value and suspends frame
    
resume later from same point
    
return or end raises StopIteration
```ジェネレーターは、バイトコードの実行、フレーム、例外処理、反復、遅延評価、メモリの有効期間、およびクリーンアップ セマンティクスを接続します。

これらは、CPython が実行状態をオブジェクトとして扱う最も明確な例の 1 つです。