在编译时(或者通过静态分析器(因为我的示例是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中定义的,所以我们弹出包围函数并在那里找到一个,这就是f中的a所指的。源代码中的位置足以告诉我,f从一个封闭的作用域获取a的值。
但是正如所描述的这里,当一个函数被调用时,它的本地框架扩展了它的父环境。因此,在运行时进行环境查找是没有问题的。但我不确定的是,静态分析器总是能够在代码运行之前,在编译时确定引用哪个闭包。在上面的示例中,很明显,elvis有两个闭包,并且很容易跟踪它们,但是其他情况就不是那么简单了。直觉上,我感到紧张的是,静态分析的尝试通常会遇到一个停滞的问题。
那么,词法作用域是否有一个动态的方面,在源代码中的位置告诉我们,包含了一个封闭的作用域,但不一定是引用了哪个闭包?或者,在编译器中,这是一个已解决的问题,函数中对其闭包的所有引用都可以静态地详细计算出来吗?
或者答案取决于编程语言--在这种情况下,词汇范围并不像我想象的那么强?
[编辑@注释:
就我的例子而言,我可以重申我的问题:我读到了诸如“在编译时可以确定词汇解析”这样的声明,但是我想知道如何在编译时(通常)静态地/在编译时计算出对a和f2值的引用。
解决办法是,词法范围没有那么多要求。L.S.可以在编译时告诉我们,每当我在a中时,就会定义一些叫做f的东西(这显然可以静态地计算出来;这是词法作用域的定义),但是确定它实际使用的值(或者哪个闭包是活动的)超出了L.S的概念,2)在运行时(不是静态的)完成的,所以在某种意义上是动态的,但当然3)使用了不同于动态范围的规则。
引用@PatrickMaupin的话,外卖信息是“一些动态的工作仍有待完成”。
发布于 2015-09-22 22:32:57
闭包可以通过多种方式实现。其中之一就是捕捉环境..。换句话说,考虑一下这个例子
def foo(x):
y = 1
z = 2
def bar(a):
return (x, y, a)
return barenv捕获解决方案如下:
foo并构建包含x、y、z、bar名称的本地框架。名称x绑定到参数,名称y和z绑定到1和2,名称bar绑定到闭包。bar的闭包实际上捕获了整个父帧,因此当调用它时,它可以在自己的本地帧中查找名称a,并可以在捕获的父帧中查找x和y。使用这种方法(即而不是使用的方法),只要闭包仍然有效,变量z就会保持活动,即使闭包没有引用它。
另一个选项,稍微复杂一些,可以实现如下工作:
bar的闭包,从当前作用域捕获名称x和y。这需要在创建闭包时支付额外的时间,因为每个捕获的单元格都需要在闭包对象中复制(而不是仅仅复制指向父帧的指针),但是它的优点是没有捕获整个框架,因此z在foo返回后不会保持活跃,只有x和y会。
这就是Python做的..。基本上在编译时,当发现一个闭包(一个命名的函数或一个lambda)时,执行一个子编译。在编译期间,当有解析为父函数的查找时,变量被标记为单元格。
一个小麻烦是,当捕获参数(如foo示例)时,还需要在序言中执行额外的复制操作,以转换单元格中传递的值。在Python中,这在字节码中是不可见的,而是由调用机器直接完成的。
另一个烦恼是,即使在父上下文中,每次对捕获变量的访问都需要双重间接方向。
其优点是闭包只捕获真正引用的变量,当它们不捕获任何代码时,生成的代码与常规函数一样高效。
要了解这在Python中是如何工作的,可以使用dis模块检查生成的字节码:
>>> dis.dis(foo)
2 0 LOAD_CONST 1 (1)
3 STORE_DEREF 1 (y)
3 6 LOAD_CONST 2 (2)
9 STORE_FAST 1 (z)
4 12 LOAD_CLOSURE 0 (x)
15 LOAD_CLOSURE 1 (y)
18 BUILD_TUPLE 2
21 LOAD_CONST 3 (<code object bar at 0x7f6ff6582270, file "<stdin>", line 4>)
24 LOAD_CONST 4 ('foo.<locals>.bar')
27 MAKE_CLOSURE 0
30 STORE_FAST 2 (bar)
6 33 LOAD_FAST 2 (bar)
36 RETURN_VALUE
>>>如您所见,生成的代码使用1 (写入单元格的操作,从而使用双重间接方向)将2存储到y中,并使用STORE_FAST将2存储到z中(在当前帧中,z没有被捕获,只是本地的)。当foo的代码开始执行时,x已经被调用机器包装到一个单元中。
bar只是一个局部变量,所以STORE_FAST用来编写它,但是要构建闭包x和y需要单独复制(在调用MAKE_CLOSURE操作代码之前,它们被放入一个元组中)。
闭包本身的代码可见于:
>>> dis.dis(foo(12))
5 0 LOAD_DEREF 0 (x)
3 LOAD_DEREF 1 (y)
6 LOAD_FAST 0 (a)
9 BUILD_TUPLE 3
12 RETURN_VALUE您可以看到,在返回的闭包中,x和y是用LOAD_DEREF访问的。无论在嵌套函数层次结构中定义了多少“向上”变量,它实际上只是一个双间接的距离,因为在构建闭包时付出了代价。相对于本地变量,闭包变量的访问速度(按常量因素)稍微慢一些.不需要在运行时遍历“范围链”。
更复杂的编译器,如SBCL (用于生成本机代码的Common的优化编译器),也会进行“转义分析”,以检测闭包是否真的能经受住封闭的功能。如果不发生这种情况(即,如果bar仅在foo中使用,而不是存储或返回),则可以在堆栈中而不是堆上分配单元格,从而降低运行时"consing“的数量(在堆上分配需要回收垃圾收集的对象)。
这种区别在称为“向下/向上”的文献中存在;也就是说,如果捕获的变量仅在较低级别(即在闭包中或在闭包内创建的更深的闭包中)或在较高级别(即,如果我的调用方能够访问我捕获的本地用户)可见。
要解决向上的funarg问题,需要一个垃圾收集器,这就是为什么C++闭包没有提供这种功能的原因。
发布于 2015-09-22 22:11:34
这是一个解决的问题..。不管是哪种方式。Python使用纯粹的词法作用域,闭包是静态确定的。其他语言允许动态作用域--并且闭包是在运行时确定的,在运行时调用堆栈上搜索,而不是在解析堆栈上搜索。
这是否足够的解释呢?
发布于 2015-09-22 22:12:06
在Python中,如果变量被分配给(出现在赋值的LHS上),并且没有显式声明为全局或非本地变量,则该变量被确定为本地变量。
因此,可以通过计算词法作用域链来静态地确定将在哪个函数中找到哪个标识符。但是,仍然需要做一些动态工作,因为可以任意嵌套函数,所以如果函数A包括函数B,其中包含函数C,那么函数C要从函数A访问变量,就必须为A找到正确的框架(闭包也是一样的)。
https://stackoverflow.com/questions/32727556
复制相似问题