提问人:leonbear 提问时间:7/27/2018 最后编辑:Mad Physicistleonbear 更新时间:7/27/2018 访问量:602
如何在嵌套函数中计算来自外部作用域的变量?
How do variables from an outer scope get evaluated in nested functions?
问:
代码优先:
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什么?似乎除了函数对象的引用之外,还有更多的东西。
答:
Python 中的函数对象不仅仅是函数,它们是闭包:1 它们带有对执行语句的本地环境的引用。def
特别是,可以从内部访问内部的局部变量。(即使你 ,这些值保持活动状态,因此只要存在,闭包仍然有效。outer_func
inner_func
return inner_func
inner_func
如果在 中添加语句,它甚至可以从 的主体中重新分配局部变量。nonlocal
inner_func
outer_func
这是如何工作的?
好吧,语句2 只是一个语句,就像任何其他语句一样。它的作用是这样的:def
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
如果一个变量本来是局部的,但嵌套函数引用了该变量而没有赋值给它,或者有一个语句,那么它在外部函数中是一个单元格变量,在内部函数中是一个自由变量。3
nonlocal
当解释器调用函数时,它会创建一个对象,该对象保存本地命名空间,即对函数的所有局部变量的引用。
frame
但是单元格变量很特殊:解释器为每个变量创建一个特殊对象,并且对该单元格的引用进入命名空间,因此每次访问或更改值时,该值前面都会有一个额外的取消引用。
cell
上面的伪代码的作用是将单元格从外部函数的框架复制到嵌套函数上名为 的特殊属性。
_make_closure
__closure__
然后,当您调用内部函数时,解释器会将这些单元格从 复制到该函数的框架中。
__closure__
因此,外部函数的框架和内部函数的框架都引用了相同的单元格,这就是它们共享变量的方式。
有关更多信息,请参阅 inspect
模块的文档,它向您展示了如何在交互式解释器中查找 和 之类的内容,以及 dis
模块,该模块可让您查看函数编译的实际字节码。__closure__
co_freevars
1. 这是具有一系列相关但不同含义的词之一。“闭包”可以表示在函数中捕获本地命名空间的技术,也可以表示捕获的命名空间,也可以表示附加了捕获的命名空间的函数,也可以表示捕获的命名空间中的某个变量。通常,从上下文中可以明显看出您指的是哪一个。如果没有,你必须说“闭包捕获”或“闭包函数”或“闭包变量”之类的话。
2. 如果您想知道,lambda
表达式的工作方式与 def
语句完全相同。类
定义并不相同,而是相似的。
3. 如果你有多层嵌套,它实际上会更复杂,但让我们忽略这一点。
评论
如果函数与函数对象,您似乎混淆了代码。读取源文件时,仅计算一次代码对象。但是,每次调用时都会创建一个调用的新函数对象。发生这种情况是因为语句是一种赋值类型:它将函数对象与指定名称相关联。inner_func
outer_func
def
函数对象理所当然地包含对其代码的引用,以及对它需要操作的所有命名空间的引用,包括其父级的非本地命名空间和全局模块命名空间。
因此,in 的值将是调用时的任何值。引用的是命名空间,而不是名称本身。如果返回(想想装饰器),命名空间将是固定的,并且只能通过对它的特殊引用来访问。pa
inner_func
outer_func
outer_func
inner_func
评论
__globals__
评论
nonlocal
LOAD_DEREF