首页 新闻 会员 周边

__getattr__,__delattr__,__setattr__也是每个类默认有这个方法吗,如果没有重载这些方法,会被默认拦截吗

0
[已关闭问题] 关闭于 2026-06-05 15:11

Python 3.x 中的所有类都默认继承自基类 object。而 object 类不仅提供了默认的 __getattribute__,同样也为 __setattr____delattr____getattr__ 提供了底层的默认实现(由 C 语言编写)。

问题补充:

:它们依然会被“拦截”,只不过执行的是 object 类用 C 语言写好的“默认处理逻辑”。

这是一个非常敏锐且直击 Python 底层机制的好问题!
简单来说,答案是:它们依然会被“拦截”,只不过执行的是 object 类用 C 语言写好的“默认处理逻辑”。
为了让你彻底明白,我们需要区分两个概念:“触发魔术方法”和“你手动重写(重载)魔术方法”。

1. 如果没有重载,谁来干活?

当你没有在自己的类里定义这些方法时,Python 会顺着继承链往上找,最终找到基类 objectobject 类在底层(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 的默认实现)。
  • 重载之后:你在检查站加了自定义的规矩(比如打印日志、校验数据类型)。此时,你的自定义代码会先被执行,然后你再决定是放行还是拒绝。
*Tesla*的主页 *Tesla* | 小虾三级 | 园豆:1760
提问于:2026-06-05 15:03
< >
分享
所有回答(1)
0

防坑指南:无论是重写 __setattr____delattr__ 还是 __getattribute__,由于它们是“无条件拦截”,在内部都必须使用 super().__xxx__(...)object.__xxx__(self, ...) 来委托给父类完成实际操作,否则极易引发无限递归死循环

所以,并不是说“没有重载就不拦截”,而是无论你是否重载,Python 的属性操作永远都在走这套魔术方法的底层协议。只是在你没有干预时,它表现得极其安静和自然,让你感觉不到它的存在罢了

 

果你没有重载 __getattr__,并且访问了一个存在的属性,那么:
__getattr__ 根本不会被触发(连默认逻辑也不会调用)。Python 会直接返回该属性的值。
为了让你更清晰地理解这个过程,我们可以把 Python 底层的属性查找机制拆解为以下两步:

1. 第一步:__getattribute__ 接管并成功找到

当你执行 obj.name 时,Python 无条件首先触发 __getattribute__。因为你没有重写它,所以调用的是 object 类默认的底层 C 语言逻辑。
这个默认逻辑会按照固定的顺序去查找:实例字典 (__dict__) ➔ 类字典 ➔ 父类字典。
因为你要找的 name 是存在的,所以它在第一或第二步就顺利拿到了对应的值。

2. 第二步:__getattr__ 被完全跳过

__getattr__ 的定位是“最后的兜底卫士”。它的触发条件极其苛刻:只有当 __getattribute__ 穷尽了所有查找路径,依然找不到属性并抛出 AttributeError 异常时,解释器才会退而求其次去调用 __getattr__
既然在第一步中属性已经被成功找到了,__getattr__ 
*Tesla* | 园豆:1760 (小虾三级) | 2026-06-05 15:11

基类 object 提供的默认赋值逻辑(即将属性存入 self.__dict__)一直都在内存中。但是,当你在自己的类里定义了 __setattr__ 时,Python 解释器在遇到 obj.attr = value 时,会优先执行你写的代码,而不再自动去调用父类的默认逻辑了。这就好比高速公路的收费站换成了你来管理,原来的自动抬杆机器还在,只是没人按开关了。

这里的 super().__setattr__(name, value) 就是调用了原本属于 object 的默认逻辑,完美保留了原有的赋值功能。

  • 方式一:使用 super() (最推荐、最 Pythonic)
    python
     
    class Account: def __setattr__(self, name, value): # 你的自定义扩展逻辑(比如校验年龄) if name == "age" and value < 0: raise ValueError("年龄不能为负数!") # 将其他所有属性的赋值操作,原封不动地交给父类 object 处理 super().__setattr__(name, value)
     
  • 方式二:直接操作内部字典(绕过方法调用)
    python
     
    class Account: def __setattr__(self, name, value): # 你的自定义逻辑... # 直接把值塞进实例的内部字典中 self.__dict__[name] = value
     
  • 如果你在重写 __setattr__ 时,只写了自定义逻辑,却忘记调用 super().__setattr__() 或操作 self.__dict__,那么原有的赋值功能就会真的丢失。对象将无法保存任何新属性。
    更严重的是,很多初学者会犯一个致命错误:在 __setattr__ 内部直接使用 self.name = value 来赋值。因为 self.name = value 本身又会触发 __setattr__,这就会导致程序陷入无限递归(死循环)
支持(0) 反对(0) *Tesla* | 园豆:1760 (小虾三级) | 2026-06-05 15:39
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册