不能随意写入。直接修改__dict__虽可批量赋值且绕过__setattr__,但会跳过属性校验、描述符逻辑及__slots__限制,仅适用于调试或序列化等特定场景;日常应优先使用setattr()。

直接修改 __dict__ 能否安全赋值?
不能随意写入。Python 对象的 __dict__ 是一个普通字典,但仅对「有 __dict__ 的实例」有效——比如没定义 __slots__ 的类实例。一旦类用了 __slots__,实例就没有 __dict__,直接写 obj.__dict__['x'] = 1 会抛 AttributeError。
常见错误现象:AttributeError: 'MyClass' object has no attribute '__dict__',往往就是踩了这个坑。
- 先用
hasattr(obj, '__dict__')或getattr(obj, '__dict__', None)判断是否存在 - 若类定义了
__slots__ = ('a', 'b'),只能赋值这些字段,__dict__不可用 - 使用
dataclass或NamedTuple时也默认禁用__dict__(除非显式加__dict__到__slots__或设unsafe_hash=False等)
__dict__.update() 和逐项赋值的区别
两者语义不同:update() 是批量覆盖字典内容,不触发任何属性 setter;而 obj.attr = val 会走 __setattr__,可能含校验、缓存更新等逻辑。
实操建议:
- 若只需快速填充原始数据(如从 JSON 解析后塞进对象),
obj.__dict__.update(data_dict)简洁高效 - 若字段有 property setter 或需要验证(例如
@age.setter检查是否为正整数),必须用点号赋值,__dict__绕过校验,等于“裸写内存” -
update()不会删除原__dict__中已存在但新字典里没有的键——它只增/改,不删
动态赋值时如何避免覆盖内置方法或特殊属性?
直接往 __dict__ 写键名,如果撞上方法名(如 '__init__')、描述符(如 '__get__')或 Python 内部属性(如 '_weakref_'),会导致对象行为异常甚至崩溃。
安全做法:
- 过滤掉以双下划线开头和结尾的键:
{k: v for k, v in data.items() if not k.startswith('__') or not k.endswith('__')} - 检查目标 key 是否已是方法:
callable(getattr(obj.__class__, k, None)),是则跳过 - 更稳妥的做法:用
setattr(obj, k, v)替代直接写__dict__,它内部会做合法性检查(虽然慢一点)
替代方案:为什么有时该用 setattr() 而不是 __dict__?
因为 setattr() 是 Python 官方支持的动态赋值接口,兼容所有情况:有 __slots__、有 property、有 __setattr__ 自定义逻辑、甚至 C 扩展类(只要实现了 setattro)。
对比示例:
class Person:
def __init__(self, name):
self._name = name
@property
def name(self):
return self._name.title()
@name.setter
def name(self, v):
if not isinstance(v, str):
raise TypeError("name must be str")
self._name = v
<p>p = Person("alice")
setattr(p, 'name', 'bob') # ✅ 触发 setter,校验通过
p.<strong>dict</strong>['name'] = 'bob' # ❌ 不触发 setter,绕过校验,且可能被 property 屏蔽
</p>
真正需要 __dict__ 的场景其实很窄:高频批量初始化、序列化反序列化中间层、调试时临时打补丁。日常动态赋值,优先选 setattr()。
容易被忽略的是:即使对象有 __dict__,它的键也不一定全等于可写的属性名——比如 property 可读写,但不会出现在 __dict__ 里;而某些纯数据字段可能在 __dict__ 里却不可直接赋值(受 __slots__ 限制)。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











