news 2026/2/25 6:30:58

Python:类 __dict__ 详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python:类 __dict__ 详解

在 Python 的对象模型中,类本身也是对象。既然是对象,它就拥有自己的命名空间(namespace),而这个命名空间正是通过类的 __dict__ 属性来承载的。

如果说实例 __dict__ 负责存储实例对象的状态(State),那么类 __dict__ 则负责定义行为与结构(Behavior & Structure)。深入理解类的 __dict__,是掌握 Python 中类属性查找、描述符机制、继承体系以及元编程的关键基础。

一、类 __dict__ 的本质:mappingproxy

类 __dict__ 是一个映射对象,存储了类级命名空间中的所有成员。

class A: x = 1 def f(self): pass print(A.__dict__)

其核心内容包括:

• 类属性:如 x。

• 方法函数:如 f(在类 __dict__ 中以原始函数形式存在)。

• 描述符对象:property、classmethod、staticmethod。

• 内置元数据:自动生成的属性如 __module__、__doc__、 __dict__、__weakref__ 等。

类对象的命名空间(即类 __dict__)里存储的是一切被定义在类体中的对象,而这些对象在被访问时,会根据它们是否实现了描述符协议,表现出不同的身份。

说明:

存储在 A.__dict__ 键值对中的键 “__dict__”,既不是类 __dict__,也不是实例 __dict__,它对应的值是一个 getset_descriptor(属性描述符)。当调用 obj.__dict__ 时,实际上是触发了类中的这个描述符,它负责去实例的内存地址中通过偏移量找到属于该实例的 __dict__。

需要注意的是,类 __dict__ 并不是普通的字典(dict):

type(A.__dict__) # <class 'mappingproxy'>

这意味着它是一个只读的映射视图(mappingproxy),不允许进行直接修改操作。

A.__dict__['x'] = 10# TypeError: 'mappingproxy' object does not support item assignment

Python 的设计初衷是,类结构的修改必须通过属性赋值语义(如 setattr(A, 'x', 10) 或 A.x = 10)来完成。

这样做是为了确保每次修改都能触发底层的元类钩子、更新缓存,并维护描述符机制以及多继承体系下的一致行为。

二、生成时机:类创建的“快照”

类 __dict__ 并非在运行期动态累积生成的,而是在类创建阶段一次性生成的。

1、执行类体

Python 会先创建一个临时命名空间(一个普通的字典),顺序执行类体内的代码。

class A: x = 1 def f(self): pass

2、调用元类

类体执行完成后,将该命名空间传递给元类(默认是 type)。

等价于:

A = type('A', (object,), namespace)

其中 namespace 最终成为 A.__dict__ 的内容。

3、封装视图

元类完成类对象的实例化,并将该命名空间封装为 mappingproxy 并绑定到类 __dict__。

因此,类 __dict__ 是类创建完成时的一个结构快照。后续通过 A.y = 2 等方式添加或修改属性,实际上会同步更新其底层的映射内容。

三、 属性查找链中的核心角色

当访问 obj.attr 时,Python 遵循一套严格的查找优先级:

1、数据描述符:先在类及其父类的 __dict__ 中查找实现了 __set__ 方法的描述符。

2、实例 __dict__:查找实例自身的属性。

3、非数据描述符/普通属性:在类 __dict__ 中查找方法(如普通函数)或类级变量。

4、父类 __dict__:按照 MRO(方法解析顺序) 向上追溯。

5、兜底方案:触发 __getattr__()。

可见,类 __dict__ 是属性查找链中的核心节点。方法、属性、描述符均来源于类 __dict__。

示例:

class A: x = 10 a = A()a.x # 10(查找 A.__dict__ 得到)

因为 x 存在于 A.__dict__。

四、类 __dict__ 与继承、MRO 的关系

(1)子类不会复制父类 __dict__

class A: x = 1class B(A): y = 2 # A.__dict__ 仅包含 A 的成员,如 x# B.__dict__ 仅包含 B 新定义的成员,如 y

父类成员通过 MRO 查找,而非物理复制。

(2)MRO 决定类 __dict__ 的查找顺序

属性的继承是通过 MRO 链条实现的。

print(B.__mro__)# (<class '__main__.B'>, <class '__main__.A'>, <class 'object'>)

当在 B 的实例中访问 x 时,Python 先在 B.__dict__ 中查找;若未找到,则根据 B.__mro__ 进入 A.__dict__ 查找,以此类推。

B.__dict__ → A.__dict__ → object.__dict__

这种机制既保证了内存效率(无需冗余拷贝),也保证了动态性(修改父类时,子类可立即生效)。

五、类 __dict__ 与描述符机制

类 __dict__ 是描述符生效的唯一入口。

class A: @property def x(self): return 1 A.__dict__['x'] # <property object>

访问 a.x 时,实际上会触发:

property.__get__(a, A)

描述符必须存在于类的 __dict__ 中才能正常工作。实例的 __dict__ 仅用于存储数据,无法承载描述符这类行为逻辑。

六、类 __dict__ 与方法绑定机制

在类 __dict__ 中,所有的实例方法都只是普通的函数对象:

class A: def f(self): pass print(type(A.__dict__['f'])) # <class 'function'>

当通过实例访问方法时:

a.f

Python 检测到函数对象实现了描述符协议,于是自动调用:

function.__get__(a, A)

从而将函数绑定到实例上,生成一个 Bound Method 对象。

因此,方法定义在类的 __dict__ 中,实例本身并不拥有方法,实例只是提供 self 并完成方法绑定。

七、类 __dict__ 与元类写入行为

元类可以在类创建阶段直接修改即将成为类 __dict__ 的命名空间:

class Meta(type): def __new__(mcls, name, bases, namespace): namespace['added'] = 42 return super().__new__(mcls, name, bases, namespace) class A(metaclass=Meta): pass print(A.added) # 输出:42

自定义元类时,通过操作 namespace(即未来的 A.__dict__),我们可以实现自动注入属性、修改类定义等功能:

class Meta(type): def __new__(mcls, name, bases, attrs): attrs['tag'] = "Processed" # 自动注入属性 return super().__new__(mcls, name, bases, attrs) class Service(metaclass=Meta): pass print(Service.tag) # 输出: Processed

ORM 框架(如 Django, SQLAlchemy)正是利用这一点,将类属性转化为数据库字段的映射。

八、常见误解与澄清

误解 1:类属性存放在实例中

❌ 错误

✔ 正解:类属性只存在于类 __dict__ 中。实例仅存储个性化数据。

误解 2:子类会复制父类 __dict__

❌ 错误

✔ 正解:子类仅通过 MRO 查找父类成员,两者物理隔离。

误解 3:可以随意修改 A.__dict__

❌ 错误

✔ 正解:它是 mappingproxy,必须使用赋值语义(比如 A.attr = v)修改。

误解 4:方法属于实例

❌ 错误

✔ 正解:方法定义在类 __dict__ 中,访问时通过描述符动态绑定实例。

📘 小结

类 __dict__ 是类对象的命名空间,用于存储类属性、方法和描述符。它在类创建阶段由元类一次性生成,并以只读的 mappingproxy 形式暴露。类 __dict__ 是属性查找、方法绑定、描述符生效和继承体系运作的核心。子类不会复制父类 __dict__,而是通过 MRO 动态查找。

理解类 __dict__,有助于深入掌握 Python 的类模型、方法解析顺序与元编程机制。

“点赞有美意,赞赏是鼓励”

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/15 23:26:17

SSH agent forwarding避免私钥分发风险

SSH Agent Forwarding&#xff1a;在AI开发中实现安全高效的跨主机认证 在现代AI研发环境中&#xff0c;工程师常常面对这样一个矛盾&#xff1a;既要快速接入远程GPU节点进行模型训练&#xff0c;又不能牺牲系统的安全性。尤其是在使用像 PyTorch-CUDA 这类“开箱即用”的镜像…

作者头像 李华
网站建设 2026/2/23 16:42:36

conda create虚拟环境 vs 直接使用PyTorch-CUDA-v2.8镜像对比

conda create虚拟环境 vs 直接使用PyTorch-CUDA-v2.8镜像对比 在深度学习项目启动前&#xff0c;最让人头疼的往往不是模型结构设计或数据预处理&#xff0c;而是那个看似简单却暗藏陷阱的环节——环境搭建。你是否经历过这样的场景&#xff1a;代码在同事机器上跑得飞快&#…

作者头像 李华
网站建设 2026/2/24 19:20:26

PyTorch开发者大会PDT 2024亮点回顾

PyTorch-CUDA-v2.8 镜像深度解析&#xff1a;从配置地狱到开箱即用的AI开发新范式 在大模型训练动辄消耗数万 GPU 小时的今天&#xff0c;一个看似微不足道却真实存在的瓶颈正在拖慢整个行业的迭代速度——环境配置。你是否经历过这样的场景&#xff1a;论文复现失败&#xff…

作者头像 李华
网站建设 2026/2/8 11:34:00

JFET共源放大电路输入输出阻抗图解说明

JFET共源放大电路输入输出阻抗图解说明在模拟电路设计中&#xff0c;JFET&#xff08;结型场效应晶体管&#xff09;共源放大电路因其高输入阻抗、低噪声和良好的线性表现&#xff0c;成为许多前置放大系统的首选。尤其是在处理微弱信号的场景下——比如生物电信号采集、电容麦…

作者头像 李华
网站建设 2026/2/24 18:29:32

使用Altium进行工业PLC模块硬件开发从零实现

从零打造工业级PLC模块&#xff1a;Altium实战全解析在自动化产线的控制柜里&#xff0c;你总能看到一排排插卡式的PLC模块安静地运行着。它们接收传感器信号、执行逻辑运算、驱动执行机构——看似简单&#xff0c;但背后却是高密度、高抗扰、高可靠硬件设计的集大成者。如果你…

作者头像 李华
网站建设 2026/2/15 7:48:00

YOLOv11目标检测初体验:基于PyTorch-CUDA-v2.8环境

YOLO目标检测实战&#xff1a;在PyTorch-CUDA-v2.8镜像中快速上手 你有没有过这样的经历&#xff1f;明明代码写好了&#xff0c;却卡在环境配置上——torch.cuda.is_available() 死活返回 False&#xff0c;查了一堆资料才发现是 CUDA 版本和 PyTorch 不匹配&#xff1b;或者换…

作者头像 李华