35. 発電機
#35. 発電機
ジェネレーターは再開可能な関数です。通常の関数は、開始、実行、および 1 つの戻り値で終了します。ジェネレーターは、開始して値を生成し、フレームを一時停止し、後で同じ命令位置から再開して別の値を生成し、終了するまで繰り返すことができます。
ジェネレーター関数は、以下を含む関数本体です。yield。python 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.2yieldのyield式はジェネレーターの呼び出し元に値を生成し、実行を一時停止します。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 はエラーを発生させます。RuntimeError。python 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 つです。