Python: 定义 class 变量时可以访问哪些范围?

Python: what scopes are accessible when defining class variables?

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

问题:

来自https://docs.python.org/3/reference/executionmodel.html

Class definition blocks and arguments to exec() and eval() are special in the context of name resolution. A class definition is an executable statement that may use and define names. These references follow the normal rules for name resolution with an exception that unbound local variables are looked up in the global namespace. The namespace of the class definition becomes the attribute dictionary of the class. The scope of names defined in a class block is limited to the class block; it does not extend to the code blocks of methods – this includes comprehensions and generator expressions since they are implemented using a function scope. This means that the following will fail:

B 的定义中,y 是一个未绑定的局部变量,因此在全局范围(未定义)中查找,而不是在由附上 class 语句。

class 语句根本没有定义作用域;它创建一个 命名空间 传递给元 class 以构建一个新的 class.

答案:

  • 为什么B的定义可以访问全局作用域,而不能访问A的作用域?

Chepner 已经回答了这个问题,但简而言之:在全局命名空间中查找局部变量 所以 classes 嵌套在您的代码示例中的方式按预期工作。

  • 为什么 y = x + 1 应该有效而 z = y + 1 不应该?这是设计决策,还是 CPython 的未定义行为?

如上所述,它按预期工作。


  • 在计算 class 变量的值时,哪些变量/范围可访问的一般规则是什么?我什么时候应该担心我可以使用哪个范围来定义我的 class 变量?

它们不适用于具有 LEGB 规则的嵌套函数 /realpython:

“当您定义 class 时,您正在创建一个新的本地 Python 范围。在 class 的顶层分配的名称位于此本地范围内。您在 class 语句中分配的名称不会与其他地方的名称冲突。您可以说这些名称遵循 LEGB 规则,其中 class 块代表 L 级别。"

将一个 Class 嵌套在另一个 Class 中也不是很常见,通常你会这样做 Class 继承:

class B(A):
    y = A.y
    z =  y  + 1
    glb = global_var