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
看看变量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()
,我们得到这个内部函数,如果在调用之间有一段时间被调用几次,它 returns 不同的值。
我对这个简单函数的行为有疑问。 这里有一个代码:
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
看看变量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()
,我们得到这个内部函数,如果在调用之间有一段时间被调用几次,它 returns 不同的值。