考虑这两个python文件:
# file1.py
global_var = "abc"
class A:
x = 1
glb = global_var
y = x + 1
class B:
z = 3
glb = global_var
zz = B.z
print(f"{A.B.z=}")
print(f"{A.zz=}")
# file2.py
global_var = "abc"
class A:
x = 1
glb = global_var
y = x + 1
class B:
z = y + 1
glb = global_var
zz = B.z
print(f"{A.B.z=}")
print(f"{A.zz=}")
人们会期望他们做完全相同的事情。但他们没有!
$ python file1.py
A.B.z=3
A.zz=3
$ python file2.py
Traceback (most recent call last):
File "file2.py", line 4, in <module>
class A:
File "file2.py", line 8, in A
class B:
File "file2.py", line 9, in B
z = y + 1
NameError: name 'y' is not defined
问题:
- 为什么
B
的定义可以访问全局范围,而不能访问A
的范围 - 为什么
y = x + 1
应该工作而z = y + 1
不应该工作?这是一个设计决策,还是CPython的未定义行为 - 在计算类变量的值时,哪些变量/范围是可访问的,一般规则是什么?我什么时候应该担心在定义类变量时允许使用哪个作用域
来源https://docs.python.org/3/reference/executionmodel.html:
exec((和eval((的类定义块和参数在名称解析的上下文中是特殊的。类定义是可以使用和定义名称的可执行语句。这些引用遵循名称解析的正常规则,但未绑定的局部变量在全局命名空间中查找除外。类定义的名称空间成为类的属性字典。类块中定义的名称的范围仅限于类块;它不扩展到方法的代码块&这包括理解和生成器表达式,因为它们是使用函数范围实现的。这意味着以下操作将失败:
在B
的定义中,y
是一个未绑定的局部变量,因此在全局范围(未定义它的地方(中查找,而不是在封闭的class
语句创建的命名空间中查找。
class
语句根本没有定义范围;它创建了一个命名空间,该命名空间被传递给元类,以便构造一个新的类。
答案:
- 为什么B的定义可以访问全局范围,而不能访问A的范围
Chepner已经回答了这个问题,但简而言之:在全局命名空间中查找局部变量,因此类在代码示例中的嵌套方式可以正常工作。
- 为什么y=x+1应该工作,而z=y+1不应该工作?这是一个设计决策,还是CPython的未定义行为
如上所述,它可以正常工作
- 在计算类变量的值时,哪些变量/范围是可访问的,一般规则是什么?我什么时候应该担心在定义类变量时允许使用哪个作用域
它们不能像使用LEGB规则/realpython:的嵌套函数那样工作
"当您定义一个类时,您正在创建一个新的本地Python作用域。在类的顶级分配的名称位于这个本地范围内。在类语句中指定的名称不会与其他名称冲突。可以说,这些名称遵循LEGB规则,其中类块表示L级别">
同样,在另一个类中嵌套一个类也不常见,通常情况下,类继承是这样做的:
class B(A):
y = A.y
z = y + 1
glb = global_var