首页 新闻 会员 周边

类和对象都是容器,都将属性存在__dict__中来供外界访问

0
[已解决问题] 解决于 2026-07-17 18:01
```python
class supcls():
    x = 3
    y = 4
    def m(self):
        x = 33
        self.x = 333
        self.y = 444
        self.z = 555
    def n(self):
        return self.x, self.y, self.z

s1 = supcls()
s2 = supcls()
# 调用 m 方法后,通过 self.xxx 设置对象自身的属性
s1.m()
s2.m()
```
*Tesla*的主页 *Tesla* | 老鸟四级 | 园豆:2040
提问于:2026-07-17 17:46
< >
分享
最佳答案
0

你的理解**非常准确**!

在 Python 中,**类和对象本质上都是“容器”**,它们默认都使用一个名为 `__dict__` 的字典(Dictionary)来存储各自的属性,从而实现动态绑定和外界访问。

为了让你更透彻地理解,我们结合你提供的代码,把底层的 `__dict__` 机制彻底拆解开来看。

---

### 1. 代码拆解:属性到底存到了哪个 `__dict__` 里?

我们先来看看在调用 `s1.m()` 之后,各个 `__dict__` 里到底装了什么。

#### ① 类的 `__dict__`(类属性容器)
类在定义时,`x = 3` 和 `y = 4` 以及方法 `m`、`n` 都被存入了**类的 `__dict__`** 中。
```python
print(supcls.__dict__)
# 输出类似:{'x': 3, 'y': 4, 'm': <function...>, 'n': <function...>, ...}
```
*注意:类属性是所有实例共享的。*

#### ② 方法内的局部变量(不入 `__dict__`)
在 `m(self)` 方法中,`x = 33` 只是一个**局部变量**。它存在于方法的局部栈帧中,方法执行完毕后就被销毁了,**它不会进入任何 `__dict__`**。

#### ③ 对象的 `__dict__`(实例属性容器)
当执行 `s1.m()` 时,`self.x = 333`、`self.y = 444`、`self.z = 555` 这三行代码,将这 3 个属性写入了 **`s1` 对象自己的 `__dict__`** 中。
```python
print(s1.__dict__)
# 输出:{'x': 333, 'y': 444, 'z': 555}

print(s2.__dict__)
# 输出:{'x': 333, 'y': 444, 'z': 555}
```
**关键点**:`s1.__dict__` 和 `s2.__dict__` 是**两个完全独立的字典**。修改 `s1.x` 只会修改 `s1` 的字典,绝对不会影响 `s2`,也不会影响类 `supcls` 中的 `x`。这就是你所说的“对象独有的属性”。

---

### 2. 类和对象的 `__dict__` 有什么区别?

虽然它们都是用来存属性的“字典容器”,但在底层实现上有一个非常重要的区别:

* **对象的 `__dict__` 是普通的字典(`dict`)**:
你可以直接通过字典的方式去修改它(虽然不推荐,但语法允许)。
```python
s1.__dict__['w'] = 666 # 等同于 s1.w = 666
print(s1.w) # 输出 666
```

* **类的 `__dict__` 是只读的映射代理(`mappingproxy`)**:
为了保护类的内部结构(比如方法、描述器、继承关系等)不被意外破坏,Python 把类的 `__dict__` 包装成了只读模式。
```python
# 下面这行代码会报错!TypeError: 'mappingproxy' object does not support item assignment
supcls.__dict__['new_attr'] = 100

# 正确的修改类属性的方式是通过点语法或 setattr
supcls.new_attr = 100
```

---

### 3. 结合 `__dict__` 理解属性查找顺序

理解了 `__dict__`,你就能彻底明白 Python 是如何找属性的(也就是你上一个问题中提到的机制)。

当你执行 `print(s1.x)` 时,Python 的底层查找逻辑是这样的:
1. **先查实例字典**:去 `s1.__dict__` 里找 `x`。找到了(值是 333),**直接返回,查找结束**。
2. **再查类字典**:如果 `s1.__dict__` 里没有 `x`,Python 才会去 `supcls.__dict__` 里找(此时会找到 3)。
3. **查父类字典**:如果类字典也没有,就沿着继承链(MRO)去父类的 `__dict__` 里找。

**这就是为什么 `s1.x` 是 333,而 `supcls.x` 依然是 3 的原因(实例属性遮蔽了类属性)。**

---

### 4. 进阶补充:对象一定会有 `__dict__` 吗?

你的结论在 99% 的情况下是正确的,但 Python 提供了一个特例:**`__slots__`**。

如果创建一个类时,明确指定了 `__slots__`,那么**这个类的实例将不会创建 `__dict__`**。
```python
class SlotClass:
__slots__ = ['a', 'b'] # 限制对象只能有 a 和 b 两个属性

obj = SlotClass()
obj.a = 1
# print(obj.__dict__) # 这行会报错:AttributeError: 'SlotClass' object has no attribute '__dict__'
```
**为什么要这么做?**
因为字典(`__dict__`)在底层是哈希表,会消耗较多的内存。当我们需要创建成千上万个对象,且属性固定时,使用 `__slots__` 会让 Python 在底层使用更紧凑的 C 语言数组来存储属性,从而**大幅节省内存并提高访问速度**。

### 总结
你的直觉非常敏锐!**“万物皆对象,对象皆容器”**。在 Python 中,类和实例默认都通过 `__dict__` 这个字典容器来管理自己的命名空间,这也是 Python 能够实现高度动态性(随时添加、修改属性)的底层基石。

*Tesla* | 老鸟四级 |园豆:2040 | 2026-07-17 17:46
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册