我对这个简单功能的行为有疑问。这里是一个代码:
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
>>> 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
.f at 0x000001B4E5C6C158>
>>> f()
3.14
好。让我们看看它里面有什么:
>>> f.__closure__
(,)
那是什么
>>> c = f.__closure__[0]
>>> c
一个细胞?
>>> 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 (", line 3>)
10 LOAD_CONST 3 ('foo..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 (", line 3>)
10 LOAD_CONST 3 ('foo..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 (", line 5>)
20 LOAD_CONST 5 ('foo..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__
(, )
现在,它具有两个元素:
>>> 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(),我们将获得此内部函数,如果多次调用且两次调用之间有一段时间,则该函数将返回不同的值。