对词法范围的访问可以在编译时根据源代码中的位置在编译时(或者通过静态分析器,因为我的示例是用Python编写的),这似乎是司空见惯的事情。在
下面是一个非常简单的例子,其中一个函数有两个闭包,它们的a
的值不同。在
def elvis(a):
def f(s):
return a + ' for the ' + s
return f
f1 = elvis('one')
f2 = elvis('two')
print f1('money'), f2('show')
当我们阅读函数f
的代码时,当我们看到a
,它不是在{f
中的a
所指的。源代码中的位置足以告诉我f
从一个封闭范围中为a
获取一个值。在
但是正如所描述的here,当一个函数被调用时,它的局部框架会扩展其父环境。所以在运行时进行环境查找是没有问题的。但我不确定的是,静态分析器总是能够在代码运行之前,计算出编译时引用哪个闭包。在上面的例子中,elvis
有两个闭包,很容易跟踪它们,但其他情况就不那么简单了。直觉上,我很紧张静态分析的尝试可能会遇到一个总体上停滞不前的问题。在
那么词法作用域真的有一个动态的方面吗?源代码中的位置告诉我们包含一个封闭的作用域,但不一定要引用哪个闭包?或者这是编译器解决了的问题,函数中所有对闭包的引用都可以静态地详细地计算出来吗?在
或者答案取决于编程语言——在这种情况下,词法范围界定并不像我想象的那么强烈?在
[编辑@评论:
在我的例子中,我可以重申我的问题:我读过诸如“词法解析可以在编译时确定”之类的声明,但是我想知道在f1
和f2
中对a
值的引用是如何静态/在编译时计算出来的(一般情况下)。在
解决办法是,词法范围界定并不要求这么多。五十、 S.可以告诉我们,在编译时,当我在f
时,名为a
的东西将被定义(这显然可以静态地计算出来;这是词法范围的定义),但是确定它实际需要什么值(或者,哪个闭包是活动的)超出了L.S.的概念,2) 在运行时完成(不是静态的),所以在某种意义上是动态的,但是当然3)使用了与动态范围不同的规则。在
引用@PatrickMaupin的话,我们要传达的信息是“还有一些动态的工作要做。”]
在Python中,如果一个变量被赋值(出现在赋值的LHS上),并且没有显式地声明为global或nonlocal,那么它就被确定为局部变量。在
因此,可以建立词法范围链来静态地确定哪个标识符将在哪个函数中找到。但是,一些动态的工作还是要做的,因为你可以任意嵌套函数,所以如果函数A包含函数B,其中包含函数C,那么对于函数C来说,从函数A访问变量,你必须为A找到正确的框架(闭包也是一样)
这是一个已解决的问题。。。不管怎样。Python使用纯词法作用域,闭包是静态确定的。另一种方法是在调用栈运行期间,以动态方式搜索堆栈,而不是以动态方式运行堆栈。在
这个解释足够了吗?在
闭包可以通过几种方式实现。其中之一是实际捕捉环境。。。换句话说,考虑一下这个例子
环境捕获解决方案如下:
x
、y
、z
、bar
名称的局部框架。名称x
绑定到参数,名称y
和{bar
绑定到闭包bar
的闭包实际上捕获了整个父帧,因此当调用它时,它可以在它自己的本地帧中查找名称a
,并且可以在捕获的父帧中查找x
和{使用这种方法(即不是Python使用的方法),只要闭包保持活动,变量
z
将保持活动状态,即使闭包没有引用它。在另一个更复杂的实现方法是:
bar
的闭包从当前作用域捕获名称x
和{这需要在创建闭包时额外花费一点时间,因为每个捕获的单元都需要复制到闭包对象内部(而不是只复制指向父帧的指针),但它的优点是不捕获整个帧,因此例如,}返回后将不保持活动状态,只有
z
在{x
并且y
将。在这就是Python所做的。。。基本上在编译时,当发现一个闭包(命名函数或
lambda
)时,会执行子编译。在编译过程中,当存在解析为父函数的查找时,变量被标记为单元格。在一个小麻烦是,当一个参数被捕获时(就像在
foo
示例中),还需要在序言中执行额外的复制操作来转换单元中传递的值。在Python中,这在字节码中不可见,但可以通过调用机制直接完成。在另一个麻烦是,即使在父上下文中,对捕获变量的每次访问都需要一个双间接寻址。在
其优点是闭包只捕获真正引用的变量,当它们不捕获任何变量时,生成的代码与常规函数一样高效。在
要了解Python中的工作原理,可以使用
^{pr2}$dis
模块检查生成的字节码:如您所见,生成的代码使用}存储到
STORE_DEREF
将1
存储到y
(写入单元格的操作,因此使用双间接寻址),而使用STORE_FAST
将{z
中(z
没有被捕获,只是当前帧中的一个局部)。当foo
的代码开始执行时,x
已经被调用机制包装到一个单元中。在bar
只是一个局部变量,因此使用STORE_FAST
对其进行写入,但是要构建闭包x
和{MAKE_CLOSURE
操作码之前放入元组中)。在闭包本身的代码在以下情况下可见:
您可以看到,在返回的闭包}中使用
x
和{LOAD_DEREF
进行访问。无论一个变量在嵌套函数层次结构中“向上”了多少层,它实际上只是一个双间接的距离,因为代价是在构建闭包时支付的。对于局部变量,闭合变量的访问(通过常数因子)只是稍微慢一点。。。运行时不需要遍历“范围链”。在更复杂的编译器像SBCL(一个用于生成本机代码的通用Lisp的优化编译器)一样,也要进行“转义分析”,以检测闭包是否能够在封闭函数中存活下来。 如果不发生这种情况(即,如果
bar
只在foo
内部使用,并且不存储或返回),则可以在堆栈中而不是在堆上分配单元,从而降低运行时“consing”(堆上需要回收垃圾回收的对象的分配)的数量。在这种区别在文献中被称为“向下/向上funarg”;即,如果捕获的变量只在较低级别(即在闭包或在闭包内部创建的更深的闭包中)可见,或者在较高级别(即如果我的调用方将能够访问我捕获的局部变量)。在
为了解决向上的FunARG问题,需要一个垃圾收集器,这就是为什么C++闭包不提供这种能力的原因。在
相关问题 更多 >
编程相关推荐