Python 词法范围有动态方面吗?
对词法作用域的访问可以在编译时(或者通过静态分析器,因为我的示例是用Python编写的)根据源代码中的位置来完成,这似乎是很常见的 下面是一个非常简单的示例,其中一个函数有两个闭包,它们的Python 词法范围有动态方面吗?,python,programming-languages,lexical-scope,Python,Programming Languages,Lexical Scope,对词法作用域的访问可以在编译时(或者通过静态分析器,因为我的示例是用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') 我不反对这样的想法,当我们阅读
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
显然有两个闭包,很容易跟踪它们,但其他情况就不那么简单了。直觉上,我很紧张,静态分析的尝试通常会遇到停顿的问题
那么词法作用域真的有一个动态方面吗?源代码中的位置告诉我们包含一个封闭范围,但不一定指哪个闭包?或者这在编译器中是一个已解决的问题,并且函数中对闭包的所有引用都可以静态地进行详细计算
或者答案取决于编程语言——在这种情况下,词法范围并不像我想象的那样是一个强大的概念
[编辑@评论:
就我的例子而言,我可以重申我的问题:我读过“词法解析可以在编译时确定”这样的说法,但我想知道如何静态地/在编译时(通常)计算f1
和f2
中对a
值的引用
解决方案是,词法作用域并不要求太多。L.S.可以在编译时告诉我们,每当我在f
中时,就会定义一个名为a
的东西(这显然可以静态计算;这是词法作用域的定义),但要确定它实际需要什么值(或者,哪个闭包是活动的)1)超出了L.S.的概念,2)在运行时完成(不是静态的),所以在某种意义上是动态的,当然3)使用了不同于动态范围的规则
引用@PatrickMaupin的话,这句话的大意是“一些动态的工作仍有待完成。”]这是一个已解决的问题。。。不管怎样。Python使用纯粹的词法作用域,闭包是静态确定的。其他语言允许动态作用域——闭包是在运行时确定的,在运行时调用堆栈而不是解析堆栈中搜索闭包
这就足够了吗?在Python中,如果变量被赋值(出现在赋值的LHS上),并且没有显式声明为全局或非局部,则该变量被确定为局部变量
因此,可以通过词法作用域链静态确定在哪个函数中找到哪个标识符。但是,仍然需要做一些动态工作,因为您可以任意嵌套函数,因此如果函数A包含函数B,而函数B包含函数C,那么函数C要从函数A访问变量,您必须为A找到正确的框架。(闭包也是如此。)闭包可以通过多种方式实现。其中之一是实际捕获环境。。。换句话说,考虑例子
def foo(x):
y = 1
z = 2
def bar(a):
return (x, y, a)
return bar
环境捕获解决方案如下所示:
foo
,并构建包含x
,y
,z
,条
名称的本地框架。名称x
绑定到参数,名称y
和z
绑定到1和2,名称bar
绑定到闭包bar
的闭包实际上捕获了整个父帧,因此当调用它时,它可以在自己的本地帧中查找名称a
,并可以在捕获的父帧中查找x
和y
z
就会保持活动状态,即使闭包没有引用它
另一个选项的实现稍微复杂一些,例如:
bar
的闭包从当前范围捕获名称x
和y
z
在foo
返回后将不会保持活动状态,只有x
和y
将被删除
这就是Python所做的。。。基本上,在编译时,当发现闭包(命名函数或lambda
)时,执行子编译。在编译过程中,当存在解析为父函数的查找时,变量被标记为单元格
一个小麻烦是,当捕捉到一个参数时(如foo
示例中),还需要在p
>>> 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
>>>
>>> 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