Python类的__dict__究竟存储了哪些实例属性信息?

2026-05-07 11:331阅读0评论SEO资讯
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计930个文字,预计阅读时间需要4分钟。

Python类的__dict__究竟存储了哪些实例属性信息?

“它不包含任何属性、方法、描述符或内置元信息,仅记录通过self.xxx=...或直接赋值到实例上的键值对。

  • a = A() 后,a.__dict__ 是空字典 {},哪怕类里定义了 x = 10
  • a.x = 1 才会让 a.__dict__ 变成 {'x': 1};此时访问 a.x 优先查这个字典,遮蔽掉类里的同名 A.x
  • a.__dict__['y'] = 20 是合法的,等价于 a.y = 20,但绕过了 __setattr__ 钩子,可能跳过验证逻辑
  • 如果类用了 __slots__,实例将没有 __dict__ 属性,尝试访问会抛出 AttributeError

__dict__ 是 mappingproxy,不能直接写入

它不是普通 dict,而是一个只读映射视图,设计上就禁止 A.__dict__['x'] = 10 这类操作。

  • 报错信息很明确:TypeError: 'mappingproxy' object does not support item assignment
  • 修改类属性必须走语义赋值:用 A.x = 10setattr(A, 'x', 10),这样能触发元类逻辑、描述符协议和 MRO 缓存更新
  • __dict__ 里能看到函数对象(如 A.f)、@staticmethod@classmethodproperty 描述符,以及 __module____doc__ 等元数据
  • 注意那个键叫 '__dict__' 的条目:它对应的是一个 getset_descriptor,不是字典本身——调用 a.__dict__ 实际是触发这个描述符去取实例内存里的真实字典

模块 __dict__ 是最“直白”的命名空间

模块级 __dict__ 就是普通可变字典,所有顶层定义(变量、函数、类、import)都落在里面,且允许直接增删改。

  • import demo 后,demo.__dict__['x'] = 999 完全合法,demo.x 立刻变成 999
  • 它还包含解释器注入的系统键,比如 '__file__''__builtins__''__spec__',这些不能删,删了可能破坏模块行为
  • 和类/实例不同,模块 __dict__ 生命周期与解释器一致,导入即建,退出即毁

别把 __dict__ 当通用工具,小心踩坑

它暴露的是实现细节,不是设计接口。滥用会绕过封装、跳过钩子、混淆继承逻辑。

立即学习“Python免费学习笔记(深入)”;

  • obj.__dict__.update(...) 批量设属性?可能漏掉带 @property 的字段,或触发不了 __set__
  • del obj.__dict__['x'] 删除属性?不如用 delattr(obj, 'x'),后者会走完整的删除流程
  • 序列化时直接 dump obj.__dict__?遇到 __slots__ 类、C 扩展对象(如 datetime)、或有自定义 __getstate__ 的类,会出错或丢数据
  • 想“复制”实例?B(**a.__dict__) 很危险——没处理父类属性、没调用 __init__、没考虑不可序列化的对象(如文件句柄)
真正关键的分界点在于:实例 __dict__ 是可写的运行期字典,类 __dict__ 是只读的编译期快照,模块 __dict__ 是可写的顶层命名空间。三者共用同一个名字,但语义和约束完全不同。
标签:Python

本文共计930个文字,预计阅读时间需要4分钟。

Python类的__dict__究竟存储了哪些实例属性信息?

“它不包含任何属性、方法、描述符或内置元信息,仅记录通过self.xxx=...或直接赋值到实例上的键值对。

  • a = A() 后,a.__dict__ 是空字典 {},哪怕类里定义了 x = 10
  • a.x = 1 才会让 a.__dict__ 变成 {'x': 1};此时访问 a.x 优先查这个字典,遮蔽掉类里的同名 A.x
  • a.__dict__['y'] = 20 是合法的,等价于 a.y = 20,但绕过了 __setattr__ 钩子,可能跳过验证逻辑
  • 如果类用了 __slots__,实例将没有 __dict__ 属性,尝试访问会抛出 AttributeError

__dict__ 是 mappingproxy,不能直接写入

它不是普通 dict,而是一个只读映射视图,设计上就禁止 A.__dict__['x'] = 10 这类操作。

  • 报错信息很明确:TypeError: 'mappingproxy' object does not support item assignment
  • 修改类属性必须走语义赋值:用 A.x = 10setattr(A, 'x', 10),这样能触发元类逻辑、描述符协议和 MRO 缓存更新
  • __dict__ 里能看到函数对象(如 A.f)、@staticmethod@classmethodproperty 描述符,以及 __module____doc__ 等元数据
  • 注意那个键叫 '__dict__' 的条目:它对应的是一个 getset_descriptor,不是字典本身——调用 a.__dict__ 实际是触发这个描述符去取实例内存里的真实字典

模块 __dict__ 是最“直白”的命名空间

模块级 __dict__ 就是普通可变字典,所有顶层定义(变量、函数、类、import)都落在里面,且允许直接增删改。

  • import demo 后,demo.__dict__['x'] = 999 完全合法,demo.x 立刻变成 999
  • 它还包含解释器注入的系统键,比如 '__file__''__builtins__''__spec__',这些不能删,删了可能破坏模块行为
  • 和类/实例不同,模块 __dict__ 生命周期与解释器一致,导入即建,退出即毁

别把 __dict__ 当通用工具,小心踩坑

它暴露的是实现细节,不是设计接口。滥用会绕过封装、跳过钩子、混淆继承逻辑。

立即学习“Python免费学习笔记(深入)”;

  • obj.__dict__.update(...) 批量设属性?可能漏掉带 @property 的字段,或触发不了 __set__
  • del obj.__dict__['x'] 删除属性?不如用 delattr(obj, 'x'),后者会走完整的删除流程
  • 序列化时直接 dump obj.__dict__?遇到 __slots__ 类、C 扩展对象(如 datetime)、或有自定义 __getstate__ 的类,会出错或丢数据
  • 想“复制”实例?B(**a.__dict__) 很危险——没处理父类属性、没调用 __init__、没考虑不可序列化的对象(如文件句柄)
真正关键的分界点在于:实例 __dict__ 是可写的运行期字典,类 __dict__ 是只读的编译期快照,模块 __dict__ 是可写的顶层命名空间。三者共用同一个名字,但语义和约束完全不同。
标签:Python