python - 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
必须作为堆栈变量销毁?
解决方案
为什么函数 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()
,我们会得到这个内部函数,如果在调用之间有一段时间被调用多次,它会返回不同的值。
推荐阅读
- java - 软引用在java中GC的作用
- node.js - nodejs中的ibm App ID注销
- .net - 如何在 webapi 控制器方法 C# 中检索已缓存的数据
- encoding - 如果不是英语,USSD 不返回数据
- jquery - Dragula 可拖动 - 在拖动时重新定位引导网格系统:左侧 2 个 div,右侧 1 个
- android - 如何在 Kotlin 中使用 anko 删除除最新 10 条记录之外的所有记录?
- python - 如何在 Python 中使文件夹与 Azure 存储帐户保持同步?
- maven - Intellij:skipTests 标志无法识别
- java - 在java中搜索JSON对象的有效方法
- angular - Angular 4 Ahead-of-Time - 延迟加载不起作用