实例赋值直接写入__dict__,不触发类属性更新;这是遮蔽而非覆盖,类属性仍存在但被隐藏,后续访问仅查实例字典,易导致可变类属性共享污染和self.attr += 1等隐蔽覆盖陷阱。

实例赋值直接写入 __dict__,不触发类属性更新
Python 对象访问属性时,查找顺序是:实例 __dict__ → 类 __dict__ → 父类 __dict__。一旦你在实例上执行 obj.attr = value,解释器就直接在 obj.__dict__ 里塞一个新键值对,完全不碰类的 attr。这不是“覆盖”,是“遮蔽”——类属性还在那儿,只是被挡住了。
常见错误现象:hasattr(obj, 'attr') 返回 True,hasattr(Cls, 'attr') 也返回 True,但两者指向不同内存地址;你以为改了默认值,其实只给这个实例加了个私有副本。
- 调试建议:用
'attr' in obj.__dict__和'attr' in Cls.__dict__分别确认归属,比打印值更可靠 - 性能影响:后续访问
obj.attr不再查类字典,略快;但协作时别人看到obj.attr根本无法判断它是实例独有还是类默认值 - 可变类属性(如
data = [])尤其危险:所有未覆盖该属性的实例共享同一对象,obj.data.append(x)表面操作实例,实则污染全局
self.attr += 1 是最隐蔽的覆盖陷阱
这个表达式看似 innocuous,实则分两步:先读 self.attr(可能从类字典 fallback 获取),再写回 self.__dict__。只要 self.attr 当前没在实例字典里,第一次 += 就会把类属性的值读出来,然后存成实例属性——从此类属性彻底失效。
正确做法是显式走类名或 self.__class__:
快速生成专业的 Python 脚本和应用代码。一键创建完整项目结构,支持CLI、API、爬虫、Bot、Django等多种项目类型,包含完整的项目结构、配置文件、依赖管理、测试、README和文档。
- 计数场景:用
self.__class__.count = self.__class__.count + 1,避免就地修改 - 继承安全:用
self.__class__.attr而非硬编码Cls.attr,防止子类重写时出错 - 注意:如果
self.attr已存在(比如之前赋过值),self.__class__.attr += 1仍会失败——它读的是类属性,但写的是实例字典,左右手互搏
方法、类属性、实例属性三者同名时查找链彻底失效
当 name 同时是实例属性、类方法和类属性时,Python 查找优先级仍是:实例 __dict__ → 类 __dict__(含方法)→ 父类。只要实例字典里有 name 键,哪怕它是个字符串,obj.name() 就会报 TypeError: 'str' object is not callable。
典型错误:
-
p = Person('Alice'); p.name()——p.name是字符串,不是方法 - 补救方式极有限:只能用
Person.name(p)显式调用,或getattr(Person, 'name')(p) - 命名混淆成本远高于预防成本:实例用
_name或full_name,方法用get_name(),一眼区分语义
删除 del obj.attr 只清实例层,不恢复“原始状态”
del obj.attr 仅删除实例 __dict__ 中的键,不会让类属性“复活”或“重置”。删完再访问 obj.attr,只是重新触发查找链回落到类属性——这不是恢复,是自然 fallback。
容易被忽略的点:
- 不能靠
del obj.attr来“清理”或“重置”类属性;真要删类属性,必须写del Cls.attr - 误删后若类也没定义该属性,
obj.attr直接抛AttributeError,而不是返回 None 或默认值 - 检查是否只剩类属性可用:用
not hasattr(obj, '__dict__') or 'attr' not in obj.__dict__,再配合hasattr(Cls, 'attr')
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










