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',) 从两个方向看同一个变量:```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_freevarspython 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]
```所有 lambda 表达式都关闭同一变量`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]
```这里,每个 lambda 都有自己的默认参数值。

这使用函数默认值,而不是闭包单元。

从概念上讲:```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`属于理解范围,并且所有 lambda 都封闭在同一个单元格内。

使用默认值捕获每次迭代的值:```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]
```第二种形式重新分配名称。

## 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
```第一个改变引用的对象`xs`

第二个分配给`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`不存储在实例上。它被存储在封闭单元中。

这类似于具有一种方法和私有状态的小对象。

## 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 表达式`lambda`函数遵循与以下相同的闭包规则`def`。```python
def outer():
    x = 10
    return lambda y: x + y

fn = outer()
print(fn(5))
```输出:```text
15
```lambda 有一个自由变量`x````python
print(fn.__code__.co_freevars)
print(fn.__closure__[0].cell_contents)
```lambda 只是一个紧凑的函数表达式。它没有特殊的闭合模型。

## 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()`被调用时,它创建一个生成器对象。该生成器对象可以访问闭包单元。

有两层:```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`|
| Lambda 具有不同的闭包规则 |拉姆达和`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`、循环中的后期绑定以及通过捕获的变量保留内存。