Python类的__dict__究竟存储了哪些实例属性信息?
- 内容介绍
- 文章标签
- 相关推荐
本文共计930个文字,预计阅读时间需要4分钟。
“它不包含任何属性、方法、描述符或内置元信息,仅记录通过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 = 10或setattr(A, 'x', 10),这样能触发元类逻辑、描述符协议和 MRO 缓存更新 - 类
__dict__里能看到函数对象(如A.f)、@staticmethod、@classmethod、property描述符,以及__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__ 是可写的顶层命名空间。三者共用同一个名字,但语义和约束完全不同。本文共计930个文字,预计阅读时间需要4分钟。
“它不包含任何属性、方法、描述符或内置元信息,仅记录通过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 = 10或setattr(A, 'x', 10),这样能触发元类逻辑、描述符协议和 MRO 缓存更新 - 类
__dict__里能看到函数对象(如A.f)、@staticmethod、@classmethod、property描述符,以及__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__ 是可写的顶层命名空间。三者共用同一个名字,但语义和约束完全不同。
