首页 > 解决方案 > Python 如何销毁垃圾?

问题描述

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

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

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

标签: pythongarbage-collection

解决方案


为什么函数 f 返回 [...] 3.14?我想,在函数执行之后,整个本地命名空间都应该被破坏不是吗?

是和不是。

在这种情况下,本地命名空间中所需的变量被保留为本地定义函数的所谓“闭包”。在这种情况下,该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

查看变量pi和与 : 的three区别如何与two:two一起存储STORE_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(),我们会得到这个内部函数,如果在调用之间有一段时间被调用多次,它会返回不同的值。


推荐阅读