如何在嵌套函数中评估外部作用域的变量?

leo*_*ear 3 python parameters

代码优先:

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"
Run Code Online (Sandbox Code Playgroud)

我不知道的是,"inner_func"呼叫"outer_func"的参数,但在"outer_func"的身体.当another_func调用时,它怎么能"知道"有一个"pa"?

我的意思是,当它在"outer_func"中调用时,实际传递给another_func的是什么?似乎不仅仅是函数对象的引用.

aba*_*ert 5

Python中的函数对象不仅仅是函数,它们是闭包: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())
Run Code Online (Sandbox Code Playgroud)

<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用于查看函数编译到的实际字节码的模块.


这是具有大量相关但不同含义的词汇之一."闭包"可以表示捕获函数中的本地命名空间的技术,或者它可以表示捕获的命名空间,或者它可以表示附加了捕获的命名空间的函数,或者它可以表示捕获的命名空间中的一个变量.通常很明显你从上下文中指出哪一个.如果没有,你必须说"闭包捕获"或"闭包函数"或"闭包变量".

2.如果您想知道,lambda表达式的工作方式与def语句完全相同.和class定义各不相同,但大同小异.

如果你有多层嵌套,它实际上仍然更复杂,但让我们忽略它.