代码优先:
def another_func(func):
func()
def outer_func(pa=1, pb=2):
def inner_func():
print(pa)
print(type(inner_func))
another_func(inner_func)
if __name__ == '__main__':
outer_func()
#print "1"
我不确定“ inner_func”调用的参数是“ outer_func”,但是它在“ outer_func”的主体中。当被another_func调用时,如何“知道”有一个“ pa”?
我的意思是,当另一个在“ outer_func”中调用时,实际上传递给another_func的是什么?似乎除了函数对象的引用以外,还有其他东西。
答案 0 :(得分:5)
Python中的函数对象不仅仅是函数,它们是closures: 1 ,它们带有对执行def
语句的本地环境的引用。
特别是,可以从outer_func
内部访问inner_func
内部的局部变量。 (即使您return inner_func
,这些值也会保持活动状态,因此只要inner_func
处于活动状态,闭包仍将起作用。)
如果在nonlocal
内添加inner_func
语句,它甚至可以从outer_func
的主体中重新分配局部变量。
这是如何工作的?
好吧,def
语句 2 就像其他语句一样。它所做的是这样的:
inner_func = _make_closure(<code from compiling inner_func body>, locals())
<code from compiling inner_func body>
实际上是一个常量值-编译器会在code
时将模块中每个函数的主体编译为常量import
对象。
但是从那个_make_closure
返回的函数对象是一个动态创建的新事物,它引用了烘焙到其中的局部变量。每次运行outer_func
时,它都会从同一inner_func
创建一个新的<code>
闭包,每个闭包都捕获当前的本地环境。
细节有些复杂-在某种程度上,实现之间会有所不同,因此这将是CPython特定的。
编译器的一部分工作是弄清楚函数中每个名称是什么类型的变量。您可能已经阅读了关于全局变量与局部变量的规则(当且仅当您在函数体中某处对名称进行了赋值并且没有global
语句时,变量才是局部变量)。但是闭包使事情变得更加复杂。
如果变量本来应该是,但是嵌套函数引用该变量但未分配该变量,或者具有nonlocal
语句,则该变量为外部函数,内部函数中的自由变量。 3
当解释器调用函数时,它将创建一个frame
对象,该对象保存本地命名空间-对该函数所有本地变量的引用。
但是单元格变量是特殊的:解释器为每个单元格创建一个特殊的cell
对象,并且对该单元格的引用进入了名称空间,因此每次在值前面都有一个额外的取消引用您访问或更改它。
上面的_make_closure
伪代码所做的是将单元格从外部函数的框架复制到嵌套函数__closure__
上的特殊属性。
然后,当您调用内部函数时,解释器将这些单元格从__closure__
复制到该函数的框架中。
因此,外部函数的框架和内部函数的框架都引用相同的单元格,这就是它们可以共享变量的方式。
有关更多信息,请参见inspect
模块的文档,该文档向您展示如何在交互式解释器中找到__closure__
和co_freevars
之类的内容,以及dis
模块可以让您查看函数被编译为的实际字节码。
1。这是具有一系列相关但不同含义的单词之一。 “关闭”可以表示在函数中捕获本地名称空间的技术,或者可以表示已捕获的名称空间,或者可以表示已附加捕获的名称空间的函数,或者可以表示捕获的名称空间中的变量之一。 。通常,从上下文中明显看出您的意思。如果不是,则必须说“关闭捕获”或“关闭函数”或“关闭变量”之类的内容。
2。如果您想知道,lambda
表达式的工作方式与def
语句完全相同。而且class
的定义不完全相同,而是相似的。
3。如果您具有多层嵌套,实际上实际上仍然更加复杂,但是我们可以忽略它。
答案 1 :(得分:2)
如果函数与函数对象在一起,您似乎会混淆代码。读取源文件时,代码对象仅被评估一次。但是,每次调用inner_func
时都会创建一个名为outer_func
的新功能对象。发生这种情况是因为def
语句是一种赋值类型:它将一个函数对象与指定的名称相关联。
当然,函数对象包含对其代码的引用,以及对其将需要操作的所有名称空间的引用,包括其父级的非本地名称空间和全局模块名称空间。
因此,pa
中inner_func
的值将与调用时outer_func
中的值相同。引用指向名称空间,而不是名称本身。如果outer_func
返回(认为是装饰器),则名称空间将是固定的,并且只能通过inner_func
对其的特殊引用进行访问。