39. クロージャとセル
#39. クロージャとセル
クロージャを使用すると、入れ子になった関数は、囲んでいる関数が返された後、その関数からの変数を使用できます。```python def make_adder(n): def add(x): return x + n return add
add10 = make_adder(10)
print(add10(5))
出力:text
15
```変数nに属しますmake_adder、 しかしadd後でもそれを使用します。 CPython は、キャプチャされた変数をセル オブジェクトに移動することでこれをサポートします。内部関数はそれらのセルへの参照を保持します。
39.1 入れ子関数
ネストされた関数は、別の関数の内部で定義された関数です。```python def outer(): def inner(): return 1
return inner
```のdef innerステートメントが実行されるタイミングouter走る。関数オブジェクトを作成し、それをローカル名にバインドします。inner。
電話をかけるouter()その関数オブジェクトを返します。python fn = outer() print(fn()) の関数オブジェクトinner含まれるもの:text code object globals dictionary defaults keyword defaults annotations closure cells, if needed もしinner外部ローカル変数を参照しないため、クロージャは必要ありません。
39.2 自由変数
自由変数は、コード オブジェクトによって使用される変数ですが、それを囲むスコープ内で定義されます。```python def outer(): x = 10
def inner():
return x
return inner
```内部inner、xは自由変数です。
これを検査できます:```python def outer(): x = 10
def inner():
return x
return inner
fn = outer()
print(fn.code.co_freevars)
出力:text
('x',)
```内部コードオブジェクトは必要なレコードを記録しますx外から。
39.3 セル変数
セル変数は、内部関数によってキャプチャされる必要があるローカル変数です。```python def outer(): x = 10
def inner():
return x
return inner
```のためにouter、x内部関数が使用するため、これはセル変数です。
検査:python print(outer.__code__.co_cellvars) 出力:text ('x',) 同じ変数を 2 つの方向から見ます。```text
outer:
x is a cell variable
inner: x is a free variable
通常のローカル変数はフレーム スロット内に存在します。```python
def f():
x = 10
return x
```後`f`戻るとフレームが破壊される可能性があります。ローカルスロットは消滅します。
ただし、これはクロージャでは機能しません。```python
def outer():
x = 10
def inner():
return x
return inner
```後`outer`戻り、`inner`まだ必要です`x`。
CPython はこれを保存することでこれを解決します。`x`セルオブジェクト内。
概念的には:```text
outer frame
x slot points to cell
cell contains 10
inner function
closure tuple points to same cell
```いつ`outer`が返されると、フレームは消えることがありますが、セルは生きたままです。`inner`それを参照します。
## 39.5 セルオブジェクト
セル オブジェクトは、別の Python オブジェクトへの参照を保持する小さなコンテナです。
概念的には:```text
cell
contents -> object
```のために:```python
def outer():
x = 10
def inner():
return x
return inner
```クロージャーは次のようになります。```text
inner function
__closure__:
cell for x
contents: 10
```それを調べてください:```python
fn = outer()
print(fn.__closure__)
print(fn.__closure__[0].cell_contents)
```出力形状:```text
(<cell at 0x...: int object at 0x...>,)
10
```外枠全体ではなく、細胞が生き残るのです。
## 39.6 関数クロージャタプル
関数オブジェクトには、`__closure__`属性。```python
def outer():
x = 10
def inner():
return x
return inner
fn = outer()
print(fn.__closure__)
__closure__どちらかですNoneまたはセルオブジェクトのタプル。
この例の場合:text fn.__closure__ -> (cell_for_x,) セルの順序は次のようになります。fn.__code__.co_freevars。python print(fn.__code__.co_freevars) for cell in fn.__closure__: print(cell.cell_contents) 出力:```text
('x',)
10
次の例を使用してください。```python
def outer(a):
b = 2
def inner(c):
return a + b + c
return inner
fn = outer(10)
print(outer.__code__.co_cellvars)
print(fn.__code__.co_freevars)
print(fn.__closure__)
```出力形状:```text
('a', 'b')
('a', 'b')
(<cell ...>, <cell ...>)
```外部関数にはセル変数があります`a`そして`b`。
内部関数には自由変数があります`a`そして`b`。
返された関数は両方のセルを保持します。
## 39.8`LOAD_DEREF`クロージャ変数には逆参照バイトコードを使用してアクセスします。
のために:```python
def outer():
x = 10
def inner():
return x
return inner
inner使用しませんLOAD_FASTのためにx。クロージャアクセスを使用します。
概念的には:```text LOAD_DEREF x RETURN_VALUE
`LOAD_DEREF`セルの内容を読み取ります。
同様に、クロージャ変数への格納には逆参照指向のストア命令が使用されます。
## 39.9 クロージャの分解
使用する`dis`:
```python
import dis
def outer():
x = 10
def inner():
return x
return inner
dis.dis(outer)
fn = outer()
dis.dis(fn)
```探す:```text
MAKE_CELL
LOAD_CLOSURE
MAKE_FUNCTION
LOAD_DEREF
STORE_DEREF
```正確な命令は CPython のバージョンによって異なりますが、概念的な操作は安定しています。```text
create cell for captured local
create inner function with closure
load from closure cell inside inner
```## 39.10 クロージャの作成
CPython が入れ子関数の関数オブジェクトを作成するとき、入れ子関数が自由変数を必要とする場合はクロージャ セルをアタッチします。
概念的には:```text
outer executes
create cell for x
load inner code object
load closure cell for x
make function object with closure
return function object
```のために:```python
def outer():
x = 10
def inner():
return x
return inner
```返される関数には次のものが含まれます。```text
inner.__code__
inner.__globals__
inner.__closure__ = (cell_for_x,)
```そのクロージャタプルはどのようにして行われますか`inner`見えます`x`。
## 39.11 クロージャは値ではなく変数をキャプチャします
クロージャは、値のスナップショットではなく、セルを通じて変数をキャプチャします。```python
def outer():
x = 1
def inner():
return x
x = 2
return inner
fn = outer()
print(fn())
```出力:```text
2
```内部関数はセルの現在の内容を確認します。セルは以前に更新されました`outer`戻ってきました。
概念的には:```text
cell x initially contains 1
cell x later contains 2
inner reads cell x
```## 39.12 共有セル
複数の内部関数が同じセルを共有できます。```python
def outer():
x = 0
def get():
return x
def set_value(v):
nonlocal x
x = v
return get, set_value
get, set_value = outer()
print(get())
set_value(10)
print(get())
```出力:```text
0
10
```両方の関数は同じセルを参照します。`x`。```text
get.__closure__[0] -> cell x
set_value.__closure__[0] -> same cell x
```セルは共有の可変バインディングを提供します。
## 39.13`nonlocal`
`nonlocal`新しいローカルを作成するのではなく、囲んでいる関数変数を代入する必要があることをコンパイラに指示します。```python
def outer():
x = 0
def inc():
nonlocal x
x += 1
return x
return inc
```それなし`nonlocal`、代入により`x`地元の`inc`:
```python
def outer():
x = 0
def bad():
x += 1
return x
return bad
```電話をかける`bad()`上げる`UnboundLocalError`なぜなら`x += 1`ローカルを読み取ろうとします`x`価値を持つ前に。
と`nonlocal`, CPython は、外側のセルに対して逆参照操作を発行します。
## 39.14`global`対`nonlocal`
`global`モジュール名前空間をターゲットにします。```python
x = 0
def f():
global x
x = 10
nonlocal囲んでいる関数スコープをターゲットとします。```python
def outer():
x = 0
def inner():
nonlocal x
x = 10
|宣言 |ターゲット |
|---|---|
|`global x`|モジュールのグローバル辞書 |
|`nonlocal x`|最も近い囲み関数スコープ`x`|
|代入を伴う宣言はありません |現在のローカル スコープ |`nonlocal`モジュールグローバルをターゲットにすることはできません。囲む関数バインディングが必要です。
## 39.15 クロージャとスコープの分析
コンパイラは、シンボル テーブルの分析中にクロージャのレイアウトを決定します。
コード ブロックごとに、名前が次のように分類されます。```text
local
global explicit
global implicit
free
cell
```例:```python
def outer():
x = 1
def inner():
return x
```分類:```text
outer:
x = local, promoted to cell
inner = local
inner:
x = free
```この分類により、どのバイトコード命令が発行されるかが決まります。```text
local variable -> LOAD_FAST / STORE_FAST
global name -> LOAD_GLOBAL / STORE_GLOBAL
closure variable -> LOAD_DEREF / STORE_DEREF
```## 39.16 クロージャの存続期間
セルは、何かがそれを参照している限り存続します。
共通の所有者:```text
function closure tuple
active frame
generator frame
coroutine frame
another cell or object graph
```例:```python
def outer():
x = [1, 2, 3]
def inner():
return x
return inner
fn = outer()
```リストは生きたままです。```text
fn
__closure__
cell
list [1, 2, 3]
```いつ`fn`到達不能になった場合、クロージャータプルとセルは解放できます。また、他の参照が存在しない場合はリストも解放できます。
## 39.17 クロージャは通常、フレーム全体を生かし続けるわけではありません
よくある誤解は、クロージャーによって外側のフレーム全体が維持されるということです。
通常はそうではありません。```python
def outer():
a = "captured"
b = "not captured"
def inner():
return a
return inner
```返される関数には次のものが必要です`a`、しかしそうではありません`b`。
概念的には:```text
inner keeps cell for a
inner does not keep b
outer frame can be destroyed
```これは、フレーム全体を保存するよりも効率的です。
ただし、フレーム オブジェクト自体がイントロスペクションを通じてキャプチャされた場合は、すべてのローカルを存続させることができます。
## 39.18 ループ内の遅延バインディング
クロージャは反復ごとの値ではなく変数をキャプチャします。```python
funcs = []
for i in range(3):
funcs.append(lambda: i)
print([f() for f in funcs])
```出力:```text
[2, 2, 2]
```すべてのラムダは同じ変数上で閉じます`i`。ループが終了した後、`i`は`2`。
これは遅延バインディングです。値は関数の作成時ではなく、関数の実行時に検索されます。
## 39.19 デフォルトでの現在の値の取得
デフォルトの引数を使用して現在の値を取得します。```python
funcs = []
for i in range(3):
funcs.append(lambda i=i: i)
print([f() for f in funcs])
```出力:```text
[0, 1, 2]
```ここで、各ラムダには独自のデフォルトの引数値があります。
これはクロージャセルではなく関数のデフォルトを使用します。
概念的には:```text
lambda i=0: i
lambda i=1: i
lambda i=2: i
```## 39.20 内包表記のクロージャ
内包表記には独自のスコープがありますが、内包表記内のクロージャは遅延バインディングを示す可能性があります。```python
funcs = [lambda: x for x in range(3)]
print([f() for f in funcs])
```出力:```text
[2, 2, 2]
```ループ変数`x`は内包スコープに属し、すべてのラムダは同じセル上で閉じます。
デフォルトを使用して反復ごとの値を取得します。```python
funcs = [lambda x=x: x for x in range(3)]
print([f() for f in funcs])
```出力:```text
[0, 1, 2]
```## 39.21 クロージャと可変性
クロージャは可変オブジェクトを参照できます。```python
def outer():
xs = []
def add(x):
xs.append(x)
return xs
return add
add = outer()
print(add(1))
print(add(2))
```出力:```text
[1]
[1, 2]
```いいえ`nonlocal`この関数はリスト オブジェクトを変更するため、この関数が必要になります。名前は再バインドされません`xs`。
これは機能します:```python
xs.append(x)
```これには必要があります`nonlocal`:
```python
xs = xs + [x]
```2 番目の形式では、名前が再割り当てされます。
## 39.22 再バインドと突然変異
比較してください:```python
def outer():
xs = []
def add(x):
xs.append(x)
return add
```そして:```python
def outer():
xs = []
def add(x):
xs = xs + [x]
return add
```1 つ目は、によって参照されるオブジェクトを変更します。`xs`。
2 番目の割り当ては、`xs`、したがってコンパイラは`xs`ローカルとして`add`宣言されない限り`nonlocal`。
正しい再バインド:```python
def outer():
xs = []
def add(x):
nonlocal xs
xs = xs + [x]
return xs
return add
```## 39.23 クロージャとファンクションファクトリー
クロージャは関数ファクトリーによく使用されます。```python
def power(exp):
def apply(x):
return x ** exp
return apply
square = power(2)
cube = power(3)
print(square(5))
print(cube(5))
```出力:```text
25
125
```それぞれの電話に`power`新しいセルを作成します`exp`。```text
square closure:
exp = 2
cube closure:
exp = 3
```のコードオブジェクト`apply`は共有できますが、クロージャーセルは異なります。
## 39.24 クロージャとデコレータ
デコレータは通常、クロージャを使用します。```python
def log_calls(fn):
def wrapper(*args, **kwargs):
print("calling", fn.__name__)
return fn(*args, **kwargs)
return wrapper
```使用:```python
@log_calls
def add(a, b):
return a + b
```概念的には:```python
def add(a, b):
return a + b
add = log_calls(add)
```返された`wrapper`オリジナルの上に閉じます`fn`。```text
wrapper.__closure__
cell for fn -> original add function
```## 39.25 クロージャと状態
クロージャはプライベート状態を保存できます。```python
def counter():
n = 0
def inc():
nonlocal n
n += 1
return n
return inc
c = counter()
print(c())
print(c())
```出力:```text
1
2
```州`n`インスタンスには保存されません。閉鎖セルに保管されます。
これは、1 つのメソッドとプライベート状態を持つ小さなオブジェクトに似ています。
## 39.26 クロージャの状態とオブジェクトの状態
閉鎖バージョン:```python
def counter():
n = 0
def inc():
nonlocal n
n += 1
return n
return inc
```オブジェクトのバージョン:```python
class Counter:
def __init__(self):
self.n = 0
def inc(self):
self.n += 1
return self.n
```比較:
|特集 |終わり |オブジェクト |
|---|---|---|
|状態ストレージ |セル変数 |インスタンスの属性 |
|複数の操作 |あまり便利ではありません |ナチュラル |
|内省 |それほど明示的ではない |より明示的な |
|小さなコールバック状態 |良いフィット感 |良いフィット感 |
|豊かな行動 |気まずい |より良い |
クロージャは、キャプチャされた小さな状態に最適です。クラスは、大規模なプロトコルに適しています。
## 39.27 クロージャと参照サイクル
クロージャはサイクルに参加できます。```python
def outer():
funcs = []
def inner():
return funcs
funcs.append(inner)
return inner
fn = outer()
```参考グラフ:```text
inner function
closure cell
funcs list
inner function
```このサイクルは、到達不能な場合に CPython の循環ガベージ コレクターによって収集できます。
ファイナライザーまたは外部リソースが関与するサイクルには、さらに注意が必要です。
## 39.28 クロージャと記憶保持
クロージャは大きなオブジェクトを保持できます。```python
def make_reader():
data = bytearray(100_000_000)
def read():
return data[0]
return read
reader = make_reader()
```大きな`data`オブジェクトは存続する限り生き続ける`reader`そうです。
保持チェーン:```text
reader function
closure tuple
cell
large bytearray
```クロージャに大きなオブジェクトが必要なくなった場合は、クリアするか、キャプチャを避けてください。
## 39.29 偶発的な捕獲を避ける
これはキャプチャします`self`:
```python
class C:
def make_callback(self):
def callback():
return self.value
return callback
```返されたコールバックにより、インスタンスは存続し続けます。
場合によってはこれが意図されている場合もあります。場合によっては、予期しないリテンションが発生することがあります。
必要な値のみをキャプチャできます。```python
class C:
def make_callback(self):
value = self.value
def callback():
return value
return callback
```これでコールバックは継続します`value`必ずしもインスタンス全体ではありません。
## 39.30 クロージャの内容の検査
使用する`__closure__`気をつけて:```python
def outer():
x = {"a": 1}
def inner():
return x
return inner
fn = outer()
for name, cell in zip(fn.__code__.co_freevars, fn.__closure__):
print(name, cell.cell_contents)
```出力:```text
x {'a': 1}
```これは学習やデバッグには役立ちますが、実稼働コードがクロージャの内部に依存することはほとんどありません。
## 39.31 空のセル
一部の特殊なケースでは、クロージャ セルが空になることがあります。
削除を伴うパターンの例では、空のセルが生成される可能性があります。```python
def outer():
x = 1
def inner():
return x
del x
return inner
```電話をかける:```python
fn = outer()
fn()
```セルには内容がなくなったため、エラーが発生します。
アクセスする`cell.cell_contents`空のセルの場合は値が上がります`ValueError`。
## 39.32 クロージャーと`del`非ローカル変数を削除すると、セルがクリアされます。```python
def outer():
x = 1
def delete():
nonlocal x
del x
def read:
return x
return read, delete
```修正版:```python
def outer():
x = 1
def delete():
nonlocal x
del x
def read():
return x
return read, delete
```使用:```python
read, delete = outer()
print(read())
delete()
print(read())
```決勝戦`read()`セルが空であるため発生します。
## 39.33 クロージャとデフォルトは異なります
デフォルトは関数オブジェクトに保存されます。```python
def f(x=[]):
return x
```クロージャセルは次の場所に格納されます。`__closure__`。```python
def outer():
x = []
def inner():
return x
return inner
```検査:```python
print(f.__defaults__)
print(outer().__closure__)
```彼らはさまざまな問題を解決します。
|メカニズム |店舗 |
|---|---|
|デフォルトの引数 |引数を省略した場合の値 |
|クロージャセル |囲んでいるスコープからの変数 |
デフォルトは関数の作成時に評価されるため、遅延バインディングのデフォルト引数のトリックは機能します。
## 39.34 クロージャとラムダ`lambda`関数は以下と同じ閉包規則に従います。`def`。```python
def outer():
x = 10
return lambda y: x + y
fn = outer()
print(fn(5))
```出力:```text
15
```ラムダには自由変数があります`x`。```python
print(fn.__code__.co_freevars)
print(fn.__closure__[0].cell_contents)
```ラムダは単なるコンパクトな関数式です。特別なクロージャーモデルはありません。
## 39.35 クロージャとジェネレータ
ジェネレーターは変数を閉じることができます。```python
def outer(limit):
def gen():
for i in range(limit):
yield i
return gen
make = outer(3)
print(list(make()))
```ジェネレーター機能が終了します`limit`。
いつ`make()`が呼び出されると、ジェネレーター オブジェクトが作成されます。そのジェネレータ オブジェクトはクロージャ セルにアクセスできます。
次の 2 つの層があります。```text
generator function
closure cell for limit
generator object
suspended frame when running
references generator function/code/closure state
```## 39.36 クロージャとコルーチン
非同期関数は変数を閉じることもできます。```python
def outer(delay):
async def wait_then_return(value):
await sleep(delay)
return value
return wait_then_return
```非同期関数は終了します`delay`。
これを呼び出すとコルーチン オブジェクトが作成されます。中断中、コルーチンはフレーム状態を維持し、関数はクロージャ セルを保持します。
これにより、待機中もキャプチャされたオブジェクトを保持できます。
## 39.37 クロージャ性能
クロージャ変数へのアクセスは、通常、高速なローカル変数へのアクセスよりも遅くなります。
高速ローカル:```text
LOAD_FAST
```クロージャ変数:```text
LOAD_DEREF
LOAD_DEREFセルを読み取らなければなりません。
ほとんどのコードでは、このコストはわずかです。非常にホットなループでは、ローカル バインディングが重要になる可能性があります。
例:python def outer(value): def inner(xs): v = value total = 0 for x in xs: total += x + v return total return inner ここ、v内部は高速ローカルですinner、 その間valueセルから一度読み取られます。
39.38 クロージャのデバッグ
クロージャーの動作をデバッグするときは、以下を検査してください。python fn.__code__.co_freevars fn.__closure__ cell.cell_contents 例:```python
def outer():
x = 1
y = 2
def inner():
return x + y
return inner
fn = outer()
for name, cell in zip(fn.code.co_freevars, fn.closure): print(name, cell.cell_contents)
## 39.39 よくある誤解
|誤解 |正しいモデル |
|---|---|
|クロージャは値をコピーします |セルを通じて変数をキャプチャします。
|クロージャは外側のフレーム全体を生かします |通常、必要なセルのみを保持します。
|`nonlocal`グローバルを意味します |これは、囲んでいる関数のスコープをターゲットとしています。
|キャプチャされたリストのニーズを変更する`nonlocal`|名前の再バインドのみが必要です`nonlocal`|
|ラムダには異なるクロージャ ルールがあります。ラムダと`def`同じクロージャーモデルを使用する |
|ループ クロージャは各反復値をキャプチャします。これらはループ変数 | をキャプチャします。
|デフォルトとクロージャは同じです |デフォルトは値を保存します。クロージャはセルを格納します。
|クロージャ変数は高速ローカル変数です。これらはセルを通じてアクセスされます。
## 39.40 読書戦略
以下から始めます:```python
def outer():
x = 10
def inner():
return x
return inner
```検査:```python
import dis
fn = outer()
print(outer.__code__.co_cellvars)
print(fn.__code__.co_freevars)
print(fn.__closure__)
print(fn.__closure__[0].cell_contents)
dis.dis(outer)
dis.dis(fn)
```次にテストします:```text
nonlocal rebinding
multiple inner functions sharing one variable
lambda inside loops
default-argument capture
closures holding large objects
closures inside generators
closures inside async functions
```ケースごとに、以下を追跡します。```text
which scope binds the name
whether the name becomes a cell variable
which inner function sees it as a free variable
which function owns the closure tuple
how long the cell remains alive
```## 39.41 章の概要
クロージャは、入れ子になった関数が外側の関数スコープから変数にアクセスできるようにするための CPython のメカニズムです。外部ローカル変数が内部関数によって使用される場合、CPython はその変数をセルに格納します。内部関数は、必要なセルを含むクロージャ タプルを保持します。
コアモデルは次のとおりです。```text
outer function local used by inner function
↓
local becomes a cell variable
↓
inner function records it as a free variable
↓
function object stores closure tuple
↓
cell remains alive after outer frame returns
↓
inner function reads or writes cell contents
```クロージャは、ネストされた関数、デコレータ、関数ファクトリ、コールバック状態、`nonlocal`、ループ内の遅延バインディング、キャプチャされた変数によるメモリ保持。