Python 3.x 中的所有类都默认继承自基类 object。而 object 类不仅提供了默认的 __getattribute__,同样也为 __setattr__、__delattr__ 和 __getattr__ 提供了底层的默认实现(由 C 语言编写)。
:它们依然会被“拦截”,只不过执行的是 object 类用 C 语言写好的“默认处理逻辑”。
object 类用 C 语言写好的“默认处理逻辑”。1. 如果没有重载,谁来干活?
object。object 类在底层(C语言层面)已经为你准备好了默认的拦截和处理逻辑:__setattr__ 的默认行为:当执行 p.name = "abc" 时,它会直接把这个键值对塞进实例的内部字典(self.__dict__["name"] = "abc")中。__delattr__ 的默认行为:当执行 del p.name 时,它会直接从实例的内部字典中把这个键删掉(del self.__dict__["name"])。__getattribute__ 的默认行为:当访问属性时,它会按照固定的查找顺序(实例字典 -> 类字典 -> 父类字典)去把值找出来返回给你。2. 只有 __getattr__ 是特殊的例外
__getattr__,它的表现与其他三个完全不同:__getattr__,并且访问了一个不存在的属性,Python 在走完默认的查找流程后,发现确实找不到,就会直接抛出 AttributeError: 'XXX' object has no attribute 'YYY' 异常。它不会调用任何后备方法。__getattr__ 时,它才会被触发,用来提供兜底的返回值或自定义报错。💡 核心总结:什么是真正的“重载”?
object 的默认实现)。防坑指南:无论是重写 __setattr__、__delattr__ 还是 __getattribute__,由于它们是“无条件拦截”,在内部都必须使用 super().__xxx__(...) 或 object.__xxx__(self, ...) 来委托给父类完成实际操作,否则极易引发无限递归死循环
所以,并不是说“没有重载就不拦截”,而是无论你是否重载,Python 的属性操作永远都在走这套魔术方法的底层协议。只是在你没有干预时,它表现得极其安静和自然,让你感觉不到它的存在罢了
__getattr__,并且访问了一个存在的属性,那么:__getattr__ 根本不会被触发(连默认逻辑也不会调用)。Python 会直接返回该属性的值。1. 第一步:__getattribute__ 接管并成功找到
obj.name 时,Python 无条件首先触发 __getattribute__。因为你没有重写它,所以调用的是 object 类默认的底层 C 语言逻辑。__dict__) ➔ 类字典 ➔ 父类字典。name 是存在的,所以它在第一或第二步就顺利拿到了对应的值。2. 第二步:__getattr__ 被完全跳过
__getattr__ 的定位是“最后的兜底卫士”。它的触发条件极其苛刻:只有当 __getattribute__ 穷尽了所有查找路径,依然找不到属性并抛出 AttributeError 异常时,解释器才会退而求其次去调用 __getattr__。__getattr__ 基类 object 提供的默认赋值逻辑(即将属性存入 self.__dict__)一直都在内存中。但是,当你在自己的类里定义了 __setattr__ 时,Python 解释器在遇到 obj.attr = value 时,会优先执行你写的代码,而不再自动去调用父类的默认逻辑了。这就好比高速公路的收费站换成了你来管理,原来的自动抬杆机器还在,只是没人按开关了。
这里的 super().__setattr__(name, value) 就是调用了原本属于 object 的默认逻辑,完美保留了原有的赋值功能。
super() (最推荐、最 Pythonic)class Account: def __setattr__(self, name, value): # 你的自定义扩展逻辑(比如校验年龄) if name == "age" and value < 0: raise ValueError("年龄不能为负数!") # 将其他所有属性的赋值操作,原封不动地交给父类 object 处理 super().__setattr__(name, value) class Account: def __setattr__(self, name, value): # 你的自定义逻辑... # 直接把值塞进实例的内部字典中 self.__dict__[name] = value __setattr__ 时,只写了自定义逻辑,却忘记调用 super().__setattr__() 或操作 self.__dict__,那么原有的赋值功能就会真的丢失。对象将无法保存任何新属性。__setattr__ 内部直接使用 self.name = value 来赋值。因为 self.name = value 本身又会触发 __setattr__,这就会导致程序陷入无限递归(死循环)