【问题标题】:Does Python scoping rule fits the definition of lexical scoping?Python 范围规则是否符合词法范围的定义?
【发布时间】:2018-07-31 01:36:37
【问题描述】:

According to my programming language class,使用词法作用域

的语言

函数的主体是在 函数是定义的,而不是函数被调用的环境。

例如,SML 遵循以下行为:

val x = 1
fun myfun () =
    x
val x = 10
val res = myfun()  (* res is 1 since x = 1 when myfun is defined *)

另一方面,Python 不遵循这种行为:

x = 1
def myfun():
    return x
x = 10
myfun()  # 10 since x = 10 when myfun is called

那么为什么is Python described as using lexical scoping

【问题讨论】:

  • 好问题。它帮助我找到、研究、理解(更好地)这件事。我添加了一个简短的答案,(i) 给出了一个实际的例子,(ii) 尝试消除围绕该主题的迷雾:stackoverflow.com/a/64000265/687896。如果您愿意,我将不胜感激您的 cmets。

标签: python scope sml


【解决方案1】:

您的 Python myfun 正在使用定义它的环境中的 x 变量,但该 x 变量现在拥有一个新值。词法作用域意味着函数会记住定义它们的变量,但这并不意味着它们必须在函数定义时对这些变量的值进行快照。

您的标准 ML 代码有 两个 x 变量。 myfun 正在使用第一个变量。

【讨论】:

  • 换句话说,我们可以说Python的行为是词法作用域和赋值的结合吗? (相比之下,SML 没有赋值,所以有两个 x 变量。)
  • @Heisenberg 不,Python 使用词法作用域,但它也有可变作用域,而 ML 没有。
  • @abarnert 我不认为我听说过可变范围并且谷歌没有给出好的结果。能给个参考吗? (我对 Python 中的不可变和可变对象很熟悉,但我不认为这就是您在这里所指的)。
  • @Heisenberg 这只是意味着您可以分配给一个变量——或者,用不同的术语,在同一个命名空间内重新绑定一个变量。也许说 namespace 是可变的比 scope 更清楚一些。甚至命名空间中的变量是可变的——这是实际使用而不是设计语言的人们通常关心的。
【解决方案2】:

在 Python 中,就像在 SML 或(现代)Lisp 中一样,函数的主体在定义它的环境中进行评估。因此,所有三种语言都是词法范围的。

在 Python 和 Lisp 中,环境是可变的。也就是说,您可以为现有变量分配一个新值,这会改变该变量所属的环境。在该环境中定义的任何函数都将在该环境中进行评估——这意味着它们将看到变量的新值。

在 SML 中,环境是不可变的;环境不能改变,没有新值,所以函数是否会看到新值是毫无疑问的。

语法可能有点误导。在 ML 中,val x = 1val x = 10 都定义了一个全新的变量。在 Python 中,x = 1x = 10 是赋值语句——它们重新赋值给一个现有的变量,如果还没有那个名称,则只定义一个新变量。 (你在 Lisp 中看不到这一点,例如,letsetq 很难混淆。)


顺便说一句,具有可变变量的闭包在功能上等同于可变对象(在 OO 意义上),因此 Lisp(和 Python)的这一特性在传统上非常重要。


作为旁注,Python 实际上对全局命名空间(以及它上面的内置函数)有一些特殊的规则,因此您可能会争辩说,您的示例中的代码在技术上并不依赖于词法范围。但是如果你把整个东西放在一个函数中并调用那个函数,那么它肯定是一个词法作用域的例子,所以这里的全局问题真的不那么重要。

【讨论】:

    【解决方案3】:

    除了@abarnert 和@user2357112 的响应之外,它可能会帮助您考虑Python 代码的SML 等效项:

    val x = ref 1
    fun myfun () = !x
    val () = x := 10
    val res = myfun ()
    

    第一行声明了一个变量x,它引用一个整数并将引用的单元格设置为1。函数体取消引用x以返回被引用单元格中的值。第三行将引用的单元格设置为 10。第四行中的函数调用现在返回 10。

    我使用尴尬的val () = _ 语法来修复排序。添加该声明仅仅是因为它对x 的副作用。也可以写成:

    val x = ref 1
    fun myfun () = !x;
    x := 10;
    val res = myfun ()
    

    环境是不可变的——特别是x 总是指向同一个内存单元——但是一些数据结构(参考单元和数组)是可变的。

    【讨论】:

      【解决方案4】:

      TL;DR(下)给出了 Python 词法作用域在工作中的简单示例...

      但首先让我添加一些基本原理,以帮助理解这件事(并写下这个答案)。

      在 Python 中评估变量时有两个概念相互重叠,可能会造成一些混淆:

      • 一种是 Python 执行递归(内存范围)查找来评估变量,著名的 BGEL 顺序(内置
      • 另一个是定义和/或评估函数的范围

      正如 OP 所说,当我们可以切换变量的值(“x”)或者我们可以定义一个函数(使用“x”)时,Python 看起来并没有词法范围甚至先验地声明其中的变量(“x”);例如,

      > def f():
            print(x)
      > x = 1
      > f()
      1
      

      这可能会混淆关于变量名称绑定和内存中实际对象的知识,使变量的 的概念有些松散(与其他语言如 C 相比)。

      TL;DR

      Python 词法作用域在工作

      > def f():
            print(x)
      
      > def g(foo):
            x = 99
            foo()
            print(x)
      
      > x = 1
      > g(f)
      1
      99
      

      然后我们可以看到评估变量的递归 (BGEL) 范围搜索策略尊重定义函数“f()”的本地及以上范围

      【讨论】:

        猜你喜欢
        • 2015-06-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-21
        • 1970-01-01
        • 2023-04-01
        相关资源
        最近更新 更多