Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/301.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python:在重新分配外部函数后,闭包如何继续存在?_Python_Function_Memory_Closures - Fatal编程技术网

Python:在重新分配外部函数后,闭包如何继续存在?

Python:在重新分配外部函数后,闭包如何继续存在?,python,function,memory,closures,Python,Function,Memory,Closures,我正在学习Python中的闭包,我对这个概念理解得很好。在空闲状态下无所事事时,我想到了如果重新分配封闭函数,然后尝试调用封闭函数会发生什么: >>> def outer_function(hello): message = hello def inner_function(): print(message) return inner_function >>> function = ou

我正在学习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

我认为这很有趣,但我意识到我对内存中的闭包等的情况没有足够的了解。有人能解释一下在重新分配
外部函数后如何调用
内部函数

此行为不限于闭包。您刚才所做的是创建整个函数对象的一个副本,该副本引用了旧函数,显然,即使您创建了另一个同名函数,它仍会保持

def test(x): return 2*x
t = test
# redeclare the function 
def test(x): return 3*x
测验

>>> test(2)
6
>>> t(2)
4
>>> t is test 
False 
此外,您还可以检查
t
test
的位置,这表明它们是不同的对象

>>> t 
<function test at 0x10f9d6d90>
>>> test 
<function test at 0x10f778f28>
>>t
>>>试验
您可以看到,它们都是相同的功能,但位于不同的位置,因此是不同的对象。同样的事情也发生在你身上

在CPython(即大多数人认为只是“Python”的用C编写的引用实现)中,词汇闭包被实现为“平面闭包”(参见),它使用单元对象引用,而不是在运行时搜索框架对象(嵌套范围)的链接列表。这允许快速查找,并在返回闭包函数时改进垃圾收集

outer_函数
中的字节码专用于访问堆栈帧中的单元格对象,而不是直接引用
消息
对象。解释器在为调用设置堆栈帧时知道这一点,因为代码对象将此变量定义为单元格变量:

>>> outer_function.__code__.co_cellvars
('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',)
内部函数
中的字节码也会为
消息
的值解除对单元格对象的引用,但由于它不是对象的源,因此将其分类为自由变量:

>>> outer_function.__code__.co_cellvars
('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',)
调用
函数
时,此
闭包
元组中的单元格将加载到堆栈帧中

这一组细胞使它扁平化。无论您将作用域嵌套得有多深,
\uuuuuu闭包\uuuu
将始终传播所有所需的单元格。例如:

>>> function = outer_function('hello')
>>> type(function.__closure__[0])
<class 'cell'>
>>> function.__closure__[0].cell_contents
'hello'
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
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())
函数
b
c
不直接引用
x
,但它们必须传播单元格,以便内部函数
d
引用它


上述检查依赖于CPython实施细节。在Python3.3+中,您可以调用来检查闭包变量。例如:

>>> function = outer_function('hello')
>>> type(function.__closure__[0])
<class 'cell'>
>>> function.__closure__[0].cell_contents
'hello'
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
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())
导入检查
def外部_功能(您好):
message=你好
def内部_函数():
打印(信息)
返回内部函数
>>>函数=外部函数(“hello”)
>>>inspect.getclosurevars(函数)
ClosureVars(非本地={'message':'hello'},
globals={},
内置={'print':},
unbound=set())

谢谢。但是自由变量
消息
呢?它是否会像在
内部函数
中创建一样可用?@Kontorstol它绑定到函数对象。想象一下你的
内部功能
(或任何功能)就像一个袋子,上面有一个ID(内存中的位置)和一个名字。这个包包含里面声明的所有变量。当您创建另一个函数时,另一个包被创建为具有相同的名称,但不同的ID和不同的内容。如果我理解正确,那么说
函数
指的是原始的
外部函数
对象,而不是在CPython中是不对的。它不是那样实现的
outer_函数
实际上在您用新函数替换它之后完全被释放,因为没有任何东西引用原始的
outer_函数
对象<代码>内部_函数
实例化时引用了一个单元格对象,而封闭的
外部_函数
调用框架也引用了该单元格对象。请运行OP的原始实验,但在替换之前添加了
ref=weakref.ref(外部_函数)
。像以前一样分配
功能
,然后用新功能替换
外部功能
。您将发现调用
ref()
现在返回
None
,因为原始函数对象已被释放,因为其引用计数已降至0<代码>函数没有引用它。@eryksun该死!这对我来说是愚蠢的。显然,ref计数将降至零。它是
function=outer\u function(“hello”)
而不是
function=outer\u function
。我在想你在评论中的回答是无关紧要的。事实证明,我的闭包实际上与haha无关。CPython闭包是使用cell对象实现的。
outer_函数
中的代码专门用于访问堆栈上的单元格对象,而不是直接引用
消息
对象。
internal_函数中的代码也可以访问单元格对象。关键的细节是,调用
外部函数
时实例化的每个
内部函数
对象都是使用引用封闭变量的单元格对象的
闭包
元组创建的。例如,检查
函数。\uuuuu闭包\uuuu[0]。单元格内容
。调用
函数
时,此闭包元组的内容将加载到其堆栈中。@eryksun您应该回答这个问题!谢谢@eryksun,这很有趣。我必须承认,到目前为止,我还没有读过任何关于细胞对象的文章,所以我现在肯定会读@Kontorstol,这是CPython的内部实现细节。我只是提供一个低层次的解释,希望能解释掉任何看起来不可思议的事情。但是,不要依赖于同样的机制在其他系统中必然存在