你正在看一个闭包;Python 编译器将其标记y
为bar
自由变量,因为y
未分配给。
y
是一个本地 infoo
并且因为有一个使用该名称作为自由变量的嵌套范围,所以y
被标记为一个闭包。
当代码运行时,Pythony
在创建函数时立即创建一个闭包bar()
(每次调用时都会重新创建它foo()
;只有函数的字节码保持不变,一个附加到foo
函数代码的常量)。
只有在bar()
被调用时,Python 才需要查找y
,这意味着解除对闭包的引用,并从那里解除对y
.
您可以通过对字节码进行一些反省和反汇编来查看所有这些操作:
>>> import dis
>>> def foo():
... def bar(): return y
... y = 10
... return bar
...
>>> foo()
<function foo.<locals>.bar at 0x10d53ce60>
>>> foo()()
10
这将构建一个带有闭包的函数并在不调用的情况下返回它。这使我们能够内省bar()
由foo()
.
>>> dis.dis(foo)
2 0 LOAD_CLOSURE 0 (y)
3 BUILD_TUPLE 1
6 LOAD_CONST 1 (<code object bar at 0x10d5138a0, file "<stdin>", line 2>)
9 LOAD_CONST 2 ('foo.<locals>.bar')
12 MAKE_CLOSURE 0
15 STORE_FAST 0 (bar)
3 18 LOAD_CONST 3 (10)
21 STORE_DEREF 0 (y)
4 24 LOAD_FAST 0 (bar)
27 RETURN_VALUE
请注意 Python 编译器是如何在顶部插入一个LOAD_CLOSURE
for的。为 .创建带有附加闭包的函数对象。y
MAKE_CLOSURE
bar
>>> dis.dis(foo())
2 0 LOAD_DEREF 0 (y)
3 RETURN_VALUE
所要做bar()
的就是取消引用附加的闭包。
>>> foo.__code__.co_consts
(None, <code object bar at 0x10d5138a0, file "<stdin>", line 2>, 'foo.<locals>.bar', 10)
>>> foo.__code__.co_cellvars
('y',)
>>> foo().__closure__
(<cell at 0x10d5e2c20: int object at 0x10d188940>,)
>>> foo().__closure__[0].cell_contents
10
正如预期的那样,闭包指向y
,并且查找内容会为您提供。10
编译语言也可以做到这一点;有人设法将 Scheme 编译为 C,并保留了闭包:例如,将Scheme 编译为 C。