考虑以下 python 代码片段:
x = 1
class Foo:
x = 2
def foo():
x = 3
class Foo:
print(x) # prints 3
Foo.foo()
正如预期的那样,这会打印 3。 但是,如果我们在上面的代码片段中添加一行,行为就会改变:
x = 1
class Foo:
x = 2
def foo():
x = 3
class Foo:
x += 10
print(x) # prints 11
Foo.foo()
而且,如果我们调换上例中两行的顺序,结果会再次改变:
x = 1
class Foo:
x = 2
def foo():
x = 3
class Foo:
print(x) # prints 1
x += 10
Foo.foo()
我想了解为什么会发生这种情况,更一般地说,了解导致这种行为的范围规则。根据 LEGB 范围规则,我预计两个片段都会打印 3、13 和 3,因为封闭函数
x
中定义了 foo()
。
类块作用域很特殊。它记录在here:
类定义是一个可执行语句,可以使用和定义 名称。 这些引用遵循名称解析的正常规则 例外情况是在中查找未绑定的局部变量 全局命名空间。 类定义的命名空间成为 类的属性字典。 a 中定义的名称范围 类块仅限于类块;它并没有延伸到 方法的代码块——这包括推导式和生成器 表达式,因为它们是使用函数作用域实现的。
基本上,类块不“参与”创建/使用封闭范围。
所以,这实际上是第一个没有按照记录工作的示例。我认为这是一个真正的错误。
编辑:
好吧,实际上,这里有一些来自数据模型的更多相关文档。我认为这一切实际上都与文档一致:
类主体(大约)作为 exec(body, globals(), 命名空间)。与普通调用 exec() 的主要区别在于 词法作用域允许类体(包括任何方法) 类时来自当前和外部范围的引用名称 定义发生在函数内部。
因此类块 do 参与 using 封闭范围,但对于自由变量(无论如何都是正常的)。在我引用的第一个文档中,有关“在全局命名空间中查找未绑定局部变量”的部分适用于“通常由编译器标记为本地”的变量。因此,考虑一下这个臭名昭著的错误,例如:
x = 1
def foo():
x += 1
print(x)
foo()
这会引发未绑定的本地错误,但等效的类定义将打印
2
:
x = 1
class Foo:
x += 1
print(x)
基本上,如果类块中的任何位置有赋值语句,它就是“局部”的,但它会在全局范围内检查是否存在未绑定的局部变量,而不是抛出
UnboundLocal
错误。
因此,在第一个示例中,它不是局部变量,它只是一个自由变量,并且解析遵循正常规则。在接下来的两个示例中,您使用赋值语句,将x
标记为“本地”,因此,如果它在本地命名空间中未绑定,它将在全局命名空间中查找。