方法代码块中看不见类变量。虽然类和方法的作用域关系类似于全局作用域和函数本地作用域,但并不总是等价。例如,方法a()中无法直接访问类变量z。这就像类内部看不到全局变量一样。
1. 纠正一个误解:“类内部看不到全局变量”
这是不准确的。 在 Python 或 JavaScript 中,类内部以及方法内部完全可以看到全局变量。
以 Python 为例:
global_var = "我是全局变量"
class MyClass:
print(global_var) # ✅ 类定义体中可以直接访问全局变量
def a(self):
print(global_var) # ✅ 方法内部也可以直接访问全局变量
因此,类和方法的关系,并不等同于“屏蔽全局变量”的关系。
Python 查找变量名遵循 LEGB 规则(Local -> Enclosing -> Global -> Built-in)。
a() 中直接写 z 时,Python 会去局部找、去全局找,但会直接跳过类作用域,最终抛出 NameError。. 操作符),即 self.z 或 ClassName.z。### 第一步:打破错觉——“视觉嵌套”不等于“作用域嵌套”
**你的直觉(视觉上的错觉):**
```python
class MyClass:
z = 10 # z 在外面
def a(self):
print(z) # a 在里面,里面的函数当然能看到外面的变量啊!
```
在普通的函数嵌套(闭包)中,这完全成立:
```python
def outer():
z = 10
def inner():
print(z) # ✅ 正常打印 10,因为 inner 的作用域包裹在 outer 里
```
**但 Python 的 `class` 根本不是普通的函数嵌套!**
---
### 第二步:核心真相——类定义体只是一个“临时加工厂”
这是理解这个问题的**绝对核心**。当 Python 遇到 `class MyClass:` 时,它到底在底层干了什么?
Python 并没有创建一个“持续存在的作用域”,而是做了以下三步:
1. **开辟临时空间**:Python 创建一个**临时的局部字典(命名空间)**。
2. **执行类体代码**:Python 把 `class` 缩进里的代码(包括 `z = 10` 和 `def a(self):`)当成普通脚本,在这个临时字典里**执行一遍**。
- 此时,临时字典里有了 `{'z': 10, 'a': <function a>}`。
3. **打包并销毁(关键!)**:执行完毕后,Python 把这个临时字典**打包**,塞进一个新建的“类对象”的 `__dict__`(属性字典)里。你必须用 .(点操作符)这把特定的钥匙去开门。如果你只写一个裸名 z,Python 就绝对不去 __dict__ 里找。
然后,**那个临时的局部作用域就被彻底销毁了!**
**结论:**
方法 `a` 在定义时,它确实看了一眼外面的“临时作用域”。但是,当类创建完成后,**那个临时作用域就不存在了**。方法 `a` 被挂载到了类对象上,但它的“词法作用域(老家)”直接跳过了那个被销毁的类体,指向了**最外层的全局模块(Global)**。
所以,当你在 `a()` 里写 `z` 时,Python 去局部找(没有),去外层找(类体作用域已经灰飞烟灭了),去全局找(没有),最后报错 `NameError`。
---
一、 核心真相:Python 的两套平行宇宙
在 Python 中,找东西有两套完全不同的机制:
1. 变量查找系统(Name Resolution)—— 找“名字”
print(z)、x = 10。__dict__。2. 属性查找系统(Attribute Resolution)—— 找“属性”
self.z、MyClass.z、obj.z。__dict__(实例字典、类字典、父类字典)里找。你的问题就出在这里: 当你把类定义体打包塞进类对象的 __dict__ 后,z 就变成了一个属性。 而你在方法里直接写 print(z),触发的是变量查找系统。变量系统一看:“哦,你找的是变量啊,那我去全局和局部字典里找”,它根本不会跨界去属性系统的 __dict__ 里看一眼。