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

Why are cells needed when dealing with closures in 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