我学习了 Python 中的闭包,我对这个概念的理解非常好。在闲置时,我想过如果我重新分配封闭函数然后尝试调用封闭函数会发生什么:
>>> def outer_function(hello):
message = hello
def inner_function():
print(message)
return inner_function
>>> function = outer_function("hello")
>>> function()
hello
>>> def outer_function():
print("hi")
>>> function()
hello
我认为这很有趣,但我意识到我对内存中的闭包会发生什么等问题没有足够的了解。有人可以解释一下我如何在重新分配后调用 inner_function
outer_function
的?
最佳答案
在 CPython 中(即大多数人认为只是“Python”的用 C 编写的引用实现),词法闭包被实现为“平面闭包”(参见 PEP 227),它使用单元格对象引用而不是搜索链接运行时框架对象(嵌套范围)的列表。这允许在返回闭包函数时进行快速查找并改进垃圾收集。
outer_function
中的字节码专门用于访问栈帧中的单元格对象,而不是直接引用message
对象。解释器在为调用设置堆栈帧时知道这一点,因为代码对象将此变量定义为单元格变量:
>>> outer_function.__code__.co_cellvars
('message',)
inner_function
中的字节码也为 message
的值取消引用单元格对象,但由于它不是对象的源,因此它被归类为自由变量:
>>> type(outer_function.__code__.co_consts[1])
<class 'code'>
>>> outer_function.__code__.co_consts[1].co_name
'inner_function'
>>> outer_function.__code__.co_consts[1].co_freevars
('message',)
每个被实例化的 inner_function
函数对象都有一个 __closure__
元组,它引用包含的自由变量的单元格。例如:
>>> function = outer_function('hello')
>>> type(function.__closure__[0])
<class 'cell'>
>>> function.__closure__[0].cell_contents
'hello'
__closure__
元组中的单元格在调用 function
时加载到栈帧中。
这个单元格元组使它变平。无论您将作用域嵌套多深,__closure__
总是会传播所有需要的单元格。例如:
def a():
x = 1
def b():
def c():
def d():
x
print('d.__closure__[0].cell_contents:',
d.__closure__[0].cell_contents)
print('c.__closure__[0].cell_contents:',
c.__closure__[0].cell_contents)
c()
print('b.__closure__[0].cell_contents:',
b.__closure__[0].cell_contents)
b()
>>> a()
b.__closure__[0].cell_contents: 1
c.__closure__[0].cell_contents: 1
d.__closure__[0].cell_contents: 1
函数 b
和 c
不直接引用 x
,但它们必须传播内部函数 d 的单元格
来引用它。
以上检查依赖于 CPython 实现细节。在 Python 3.3+ 中,您可以改为调用 inspect.getclosurevars
检查闭包变量。例如:
import inspect
def outer_function(hello):
message = hello
def inner_function():
print(message)
return inner_function
>>> function = outer_function('hello')
>>> inspect.getclosurevars(function)
ClosureVars(nonlocals={'message': 'hello'},
globals={},
builtins={'print': <built-in function print>},
unbound=set())
关于Python:在重新分配外部函数后,闭包如何继续存在?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44006489/