Python:定义类变量时可以访问哪些作用域



考虑这两个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

最新更新