Python如何销毁垃圾?

How Python does destroying the garbage?

我对这个简单函数的行为有疑问。 这里有一个代码:

def foo():
    pi = 3.14
    def f():
        return pi
    return f
F = foo() 
F() # this is returning the 3.14

为什么函数 f returning 3.14?我想,在函数执行之后,整个本地命名空间应该被破坏,不是吗?所以,函数foo最后return声明的函数指针f(函数会分配到堆中),但是变量pi必须销毁为栈变量?

这段代码returns一个数字,即3.14:

def foo():
    pi = 3.14
    def f():
        return pi
    return f()

Why does the function f return [...] 3.14? I thought, after function execution the entire local namespace should be destroing is'nt?

是也不是。

在这种情况下,本地命名空间中所需的变量被保留为本地定义函数的所谓 "closure"。在这种情况下,pi 变量在此函数中保持可用,直到需要它为止。

让我们详细说明一下:

def foo():
    pi = 3.14
    def f():
        return pi
    return f

这是外部函数。

在 CLI 中,我们可以尝试一下。

>>> foo
<function foo at 0x000001B4E5282E18>
>>> dir(foo)
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']

啊,有一个像__closure__,我刚才用的那个词。这是什么?

>>> foo.__closure__
>>>

嗯?

>>> foo.__closure__ is None
True

啊。

>>> f = foo() # get the inner function
>>> f
<function foo.<locals>.f at 0x000001B4E5C6C158>
>>> f()
3.14

好的。让我们看看里面有什么:

>>> f.__closure__
(<cell at 0x000001B4E5C34A08: float object at 0x000001B4E5231648>,)

那是什么?

>>> c = f.__closure__[0]
>>> c
<cell at 0x000001B4E5C34A08: float object at 0x000001B4E5231648>

一个单元格?

>>> dir(c)
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'cell_contents']
>>> c.cell_contents
3.14

啊。所以 f.__closure__[0] 是一个单元格,类似于容器,用于从上面的本地名称空间获取的值。

作为奖励,我们可以研究函数的反汇编:

>>> import dis
>>> dis.dis(foo)
  2           0 LOAD_CONST               1 (3.14)
              2 STORE_DEREF              0 (pi)

  3           4 LOAD_CLOSURE             0 (pi)
              6 BUILD_TUPLE              1
              8 LOAD_CONST               2 (<code object f at 0x000001B4E5C418A0, file "<stdin>", line 3>)
             10 LOAD_CONST               3 ('foo.<locals>.f')
             12 MAKE_FUNCTION            8
             14 STORE_FAST               0 (f)

  5          16 LOAD_FAST                0 (f)
             18 RETURN_VALUE
>>> dis.dis(f)
  4           0 LOAD_DEREF               0 (pi)
              2 RETURN_VALUE

这里我们看看f是如何构造的:

      3           4 LOAD_CLOSURE             0 (pi)

加载变量 pi 作为闭包(单元格)

                  6 BUILD_TUPLE              1

只用这个单元格构建一个元组

                  8 LOAD_CONST               2 (<code object f at 0x000001B4E5C418A0, file "<stdin>", line 3>)
                 10 LOAD_CONST               3 ('foo.<locals>.f')
                 12 MAKE_FUNCTION            8

使用给定的名称、代码和闭包创建一个函数

                 14 STORE_FAST               0 (f)

保存起来。

在函数中,闭包元素使用LOAD_DEREF访问。


如果我们稍微扩展一下功能,比如

def foo():
    pi = 3.14
    two = 2
    three = 3
    def f():
        return pi - three
    return f

我们看看这些变量是如何处理的:

>>> dis.dis(foo)
  2           0 LOAD_CONST               1 (3.14)
              2 STORE_DEREF              0 (pi)

  3           4 LOAD_CONST               2 (2)
              6 STORE_FAST               0 (two)

  4           8 LOAD_CONST               3 (3)
             10 STORE_DEREF              1 (three)

  5          12 LOAD_CLOSURE             0 (pi)
             14 LOAD_CLOSURE             1 (three)
             16 BUILD_TUPLE              2
             18 LOAD_CONST               4 (<code object f at 0x000001B4E5CA36F0, file "<stdin>", line 5>)
             20 LOAD_CONST               5 ('foo.<locals>.f')
             22 MAKE_FUNCTION            8
             24 STORE_FAST               1 (f)

  7          26 LOAD_FAST                1 (f)
             28 RETURN_VALUE

看看变量pithreetwo的区别:twoSTORE_FAST存储,其他用STORE_DEREF以便它们可以传递给函数。

>>> foo().__closure__
(<cell at 0x000001B4E5BC41F8: float object at 0x000001B4E5231528>, <cell at 0x000001B4E5C348B8: int object at 0x0000000050816120>)

现在有两个元素:

>>> foo().__closure__[0].cell_contents
3.14
>>> foo().__closure__[1].cell_contents
3

这是它的用法:

>>> dis.dis(f)
  6           0 LOAD_DEREF               0 (pi)
              2 LOAD_DEREF               1 (three)
              4 BINARY_SUBTRACT
              6 RETURN_VALUE

减法确实发生在内部函数内部,因为变量甚至可以改变:

import time
import threading

def foo():
    c = 0
    def run():
        nonlocal c
        while c < 50:
            c += 1
            time.sleep(1.0)
    t = threading.Thread(target=run)
    t.start()
    def f(): return c
    return f

在这里,线程每秒递增变量。如果我们现在做 f = foo(),我们得到这个内部函数,如果在调用之间有一段时间被调用几次,它 returns 不同的值。