为什么在 Python 中处理闭包时需要单元格?



>我在下面的代码中理解了这一点:

a = 10
b = a
id(a) == id(b)  # True

ab是指向内存中同一对象的标签。

从函数返回闭包时,为什么需要单元格?返回的闭包的自由变量不能只是指向与父变量相同的对象的新标签吗?例如,在以下示例中:

def outer():
x = 10
def inner():
print(x)
return inner
fn = outer()
fn()  # 10

换句话说,为什么我需要(下图,而不是代码(:

outer.x -> cell -> obj(10)
inner.x -> cell -> obj(10)

并且没有(下图,而不是代码(:

outer.x -> obj(10)
inner.x -> obj(10)

?此实现试图避免什么?

因为您需要一些东西来维护对变量所引用对象的引用,而不是该对象本身。请考虑以下情况:

In [1]: def outer():
...:     x = 42
...:     def first():
...:         return x * 2
...:     def second():
...:         nonlocal x
...:         x = 0
...:         return x
...:     return first, second
...:
In [2]: f,s  = outer()
In [3]: f()
Out[3]: 84
In [4]: s()
Out[4]: 0
In [5]: f()
Out[5]: 0

请注意,单元格值保持一致性:

In [6]: f.__closure__
Out[6]: (<cell at 0x1055df890: int object at 0x101f00470>,)
In [7]: s.__closure__
Out[7]: (<cell at 0x1055df890: int object at 0x101f00470>,)
In [8]: f.__closure__[0].cell_contents
Out[8]: 0
In [9]: s.__closure__[0].cell_contents
Out[9]: 0

如果只是对这些对象的裸引用,则无法保持一致性。

只是一些扩展,请注意有特定的字节码用于操作自由变量单元格:

In [10]: import dis
In [11]: dis.dis(f)
4           0 LOAD_DEREF               0 (x)
2 LOAD_CONST               1 (2)
4 BINARY_MULTIPLY
6 RETURN_VALUE
In [12]: dis.dis(s)
7           0 LOAD_CONST               1 (0)
2 STORE_DEREF              0 (x)
8           4 LOAD_DEREF               0 (x)
6 RETURN_VALUE

这些操作不同于操作局部变量的操作,恰如其分地命名为"fast":

In [16]: dis.dis(foo)
2           0 LOAD_CONST               1 (3)
2 STORE_FAST               0 (x)
3           4 LOAD_FAST                0 (x)
6 RETURN_VALUE