python中奇怪的作用域行为

sni*_*777 47 python scope python-3.x

考虑以下python代码片段:

x = 1
class Foo:
    x = 2
    def foo():
        x = 3
        class Foo:
            print(x) # prints 3
Foo.foo()
Run Code Online (Sandbox Code Playgroud)

正如预期的那样,这会打印 3。但是,如果我们在上面的代码片段中添加一行,行为就会改变:

x = 1
class Foo:
    x = 2
    def foo():
        x = 3
        class Foo:
            x += 10
            print(x) # prints 11
Foo.foo()
Run Code Online (Sandbox Code Playgroud)

而且,如果我们在上面的例子中切换两行的顺序,结果又会发生变化:

x = 1
class Foo:
    x = 2
    def foo():
        x = 3
        class Foo:
            print(x) # prints 1
            x += 10
Foo.foo()
Run Code Online (Sandbox Code Playgroud)

我想了解为什么会发生这种情况,更一般地说,了解导致这种行为的范围规则。根据 LEGB 范围规则,我希望两个片段都打印 3、13 和 3,因为x在封闭函数中定义了一个foo()

jua*_*aga 32

类块作用域是特殊的。它记录在这里

类定义是可以使用和定义名称的可执行语句。除了在全局命名空间中查找未绑定的局部变量之外,这些引用遵循名称解析的正常规则。类定义的命名空间成为类的属性字典。类块中定义的名称范围仅限于类块;它没有扩展到方法的代码块——这包括推导式和生成器表达式,因为它们是使用函数作用域实现的。

基本上,类块不“参与”创建/使用封闭范围。

所以,它实际上是第一个没有按照文档工作的例子。我认为这是一个实际的错误。

编辑:

好的,所以实际上,这是来自数据模型的一些更相关的文档,我认为这实际上与文档一致:

类主体作为 exec(body, globals(), namespace) 执行(大约)。与普通调用 exec() 的主要区别在于,词法作用域允许类主体(包括任何方法)在类定义出现在函数内部时从当前作用域和外部作用域引用名称。

所以类块确实参与使用封闭范围,但对于自由变量(无论如何都是正常的)。在我引用的第一篇文档中,关于“在全局命名空间中查找未绑定的局部变量”的部分适用于通常由 compiler 标记为 local 的变量。因此,请考虑这个臭名昭著的错误,例如:

x = 1
def foo():
    x += 1
    print(x)

foo()
Run Code Online (Sandbox Code Playgroud)

会抛出一个未绑定的本地错误,但是一个等效的类定义:

x = 1
class Foo:
    x += 1
    print(x)
Run Code Online (Sandbox Code Playgroud)

将打印2

基本上,如果类块中的任何地方都有赋值语句,它就是“本地的”,但它会在全局范围内检查是否存在未绑定的本地而不是抛出UnboundLocal错误。

因此,在您的第一个示例中,它不是局部变量,它只是一个自由变量,并且解析遵循正常规则。在接下来的两个示例中,您使用了一个赋值语句,标记x为“本地”,因此,它将在全局命名空间中查找,以防它在本地命名空间中未绑定。