Python 的 __getattr__、__getattribute__、__setattr__ 有什么区别?
简化版
Python 提供了几个「属性访问钩子(魔术方法)」来拦截对象的属性读写:__getattribute__(拦截「所有」属性读取)、__getattr__(只在「属性找不到时」兜底)、__setattr__(拦截「所有」属性赋值)、__delattr__(拦截删除)。关键区别(最容易混的两个读取钩子):① __getattribute__(self, name)——每次访问属性 obj.x 都会调用它(不管属性存不存在),是属性查找的「总入口」,重写它风险大(容易无限递归、影响所有访问);② __getattr__(self, name)——只在「正常查找失败(属性不存在)」时才作为「兜底」被调用,是「找不到时怎么办」的钩子,安全常用(做默认值、动态属性、代理转发)。__setattr__(self, name, value)——每次 obj.x = v 都调用它(拦截所有赋值,常用于校验、只读、记录),坑是在里面赋值要用 super().__setattr__() 或 self.__dict__[name] = v(否则无限递归)。用途:动态属性、属性代理/转发、校验、惰性计算、只读对象、ORM/配置对象。核心记忆:__getattribute__ 拦截所有读取(总入口、危险)、__getattr__ 只兜底找不到的(安全常用)、__setattr__ 拦截所有赋值(内部赋值要用 super 防递归)。
详细版
四个属性访问钩子:
| 方法 | 触发时机 | 常用度 |
|---|---|---|
__getattribute__(self, name) | 每次读取属性(总入口) | 少用(危险) |
__getattr__(self, name) | 属性找不到时兜底 | 常用(安全) |
__setattr__(self, name, value) | 每次赋值属性 | 较常用 |
__delattr__(self, name) | 删除属性 del obj.x | 少用 |
# __getattr__:找不到属性时兜底(安全常用)
class DynamicConfig:
def __init__(self):
self.data = {"host": "localhost"}
def __getattr__(self, name): # 只在正常查找失败时调用
if name in self.data:
return self.data[name]
raise AttributeError(name)
c = DynamicConfig()
print(c.host) # localhost(data 里有 host,但没有 c.host 属性 → 兜底)
print(c.data) # {...}(data 是真实属性,不触发 __getattr__)
# __getattribute__:拦截所有读取(危险,每次都调)
class Logged:
def __getattribute__(self, name): # 每次 obj.x 都调用
print(f"访问 {name}")
return super().__getattribute__(name) # 必须用 super!否则无限递归
# __setattr__:拦截所有赋值(校验、只读)
class Validated:
def __setattr__(self, name, value):
if name == "age" and value < 0:
raise ValueError("年龄不能为负")
super().__setattr__(name, value) # 用 super 赋值(防递归)
v = Validated()
v.age = 25 # ✓
# v.age = -1 # ✗ ValueError
# 只读对象
class ReadOnly:
def __init__(self, x):
super().__setattr__("x", x) # 初始化时绕过(用 super)
def __setattr__(self, name, value):
raise AttributeError("只读对象,不能修改")
# 属性代理/转发(__getattr__ 的经典用途)
class Proxy:
def __init__(self, target): self._target = target
def __getattr__(self, name): # 找不到的属性 → 转发给 target
return getattr(self._target, name)
⚠️ 最关键的区分:
__getattribute__是「所有属性读取的总入口」——obj.x无论 x 存不存在都先经过它;而__getattr__是「兜底」——只有当正常的属性查找(实例字典、类、父类、描述符都找过了)都失败时,才调用它。它们的调用关系是:obj.x→ 先调__getattribute__做正常查找 → 找到就返回、找不到(抛 AttributeError)→ 才调__getattr__兜底。所以__getattr__只处理「缺失的属性」,对已存在的属性完全不干预(这让它安全、常用——做默认值、动态属性、代理转发都只影响「访问不存在的属性」);而__getattribute__干预「每一次」访问,重写它极易出错:① 容易无限递归(在里面访问self.任何属性又会触发__getattribute__,必须用super().__getattribute__(name)或object.__getattribute__)、② 影响所有属性访问、有性能开销。__setattr__同理拦截「所有赋值」,内部赋值必须用super().__setattr__()或直接改self.__dict__防递归。实践中:绝大多数场景用__getattr__(兜底)+__setattr__(校验),几乎不碰__getattribute__。
完整版教学
一、属性查找的完整流程
先理解属性访问背后的流程:
obj.x 的完整查找流程(简化):
① 调用 type(obj).__getattribute__(obj, 'x') ← 总入口
__getattribute__ 内部按顺序找:
a. 数据描述符(类里,有 __get__ 和 __set__)
b. 实例字典 obj.__dict__['x']
c. 非数据描述符 / 类属性(类及父类,走 MRO)
d. 都没找到 → 抛 AttributeError
② 如果 ① 抛了 AttributeError → 调用 __getattr__(obj, 'x') ← 兜底
__getattr__ 存在就调用(做兜底逻辑)
不存在 → AttributeError 传播出来
关键点:
__getattribute__ 是"入口",负责正常查找(每次都走)
__getattr__ 是"出口/兜底",只在正常查找失败时触发
图示:
obj.x
│
▼
__getattribute__(每次都调,做正常查找)
│
├─ 找到 → 返回值
│
└─ 没找到(AttributeError)
│
▼
__getattr__(兜底,若定义了)
│
├─ 返回值
└─ raise AttributeError(真的没有)
所以属性查找:__getattribute__总入口(做正常查找)→失败→__getattr__兜底
obj.x 的完整查找流程(简化):① 调用 type(obj).__getattribute__(obj, 'x')(总入口)——内部按顺序找:a. 数据描述符(类里、有 __get__ 和 __set__)、b. 实例字典 obj.__dict__['x']、c. 非数据描述符/类属性(类及父类走 MRO)、d. 都没找到抛 AttributeError;② 如果 ① 抛了 AttributeError → 调用 __getattr__(obj, 'x')(兜底)(存在就调用做兜底逻辑、不存在则 AttributeError 传播出来)。关键点:__getattribute__ 是「入口」负责正常查找(每次都走)、__getattr__ 是「出口/兜底」只在正常查找失败时触发。理解「属性查找:①__getattribute__总入口(每次都调,按顺序找数据描述符/实例字典/类属性)②失败(AttributeError)→__getattr__兜底(若定义了);__getattribute__入口做正常查找、__getattr__只兜底缺失的」,就理解了属性查找流程。
二、getattr:找不到时兜底
理解 getattr 的安全用法:
__getattr__(self, name):只在"属性找不到"时调用
→ 对已存在的属性完全不干预(安全)
触发条件(重要):
只有正常查找失败(属性不在实例字典、类、父类里)才调用
obj.existing # 存在 → 不触发 __getattr__
obj.missing # 不存在 → 触发 __getattr__
典型用途:
① 动态属性/默认值:
def __getattr__(self, name):
return f"默认_{name}" # 任何不存在的属性返回默认值
② 从字典/后端取值(配置、ORM):
def __getattr__(self, name):
if name in self._data:
return self._data[name]
raise AttributeError(name) # 没有就抛(保持正常语义)
③ 属性代理/转发(包装对象):
def __getattr__(self, name):
return getattr(self._wrapped, name) # 转发给被包装对象
→ 自己没有的属性/方法都转发给内部对象
④ 惰性属性(第一次访问才计算):
def __getattr__(self, name):
if name == "expensive":
value = compute()
self.expensive = value # 存下来,下次直接命中(不再兜底)
return value
注意:
① 找不到时应 raise AttributeError(别静默返回 None,破坏语义)
② 访问 self 的真实属性不会触发(只兜底缺失的)
③ __getattr__ 里访问不存在的属性会递归(小心)
所以__getattr__只兜底找不到的属性(安全);用于动态属性/默认值/代理转发/惰性
__getattr__(self, name):只在「属性找不到」时调用——对已存在的属性完全不干预(安全)。触发条件(重要):只有正常查找失败(属性不在实例字典、类、父类里)才调用(obj.existing 不触发、obj.missing 触发)。典型用途:① 动态属性/默认值(return f"默认_{name}")、② 从字典/后端取值(配置、ORM)(if name in self._data: return self._data[name] 否则 raise AttributeError)、③ 属性代理/转发(包装对象)(return getattr(self._wrapped, name) 转发给被包装对象)、④ 惰性属性(第一次访问才计算)(self.expensive = value 存下来、下次直接命中不再兜底)。注意:① 找不到时应 raise AttributeError(别静默返回 None)、② 访问 self 的真实属性不触发、③ __getattr__ 里访问不存在的属性会递归。理解「__getattr__只兜底找不到的属性(安全、已存在的不干预);用途动态属性/默认值/字典取值/代理转发(getattr(self._wrapped,name))/惰性;注意找不到要 raise AttributeError、里面访问不存在属性会递归」,就掌握了 __getattr__。
三、getattribute:拦截所有读取
理解 getattribute 的危险与用法:
__getattribute__(self, name):每次读取属性都调用(总入口)
→ 拦截"所有"属性访问(不管存不存在)
危险点1:无限递归(最常见坑)
✗ def __getattribute__(self, name):
print(self.something) # 访问 self.x → 又触发 __getattribute__ → 无限递归
return self.__dict__[name] # 同样递归!
✓ def __getattribute__(self, name):
return super().__getattribute__(name) # 用 super(或 object.__getattribute__)
→ 在里面访问任何属性都会再次触发它,必须用 super 打破递归
危险点2:影响所有访问 + 性能
每次 obj.x 都调用 → 性能开销、易引入 bug
→ 一个疏忽就影响整个对象的所有属性
正确用法(罕见):
def __getattribute__(self, name):
# 拦截逻辑(如日志、访问控制)
if name.startswith("_secret"):
raise AttributeError("禁止访问")
return super().__getattribute__(name) # 转发给正常查找
什么时候用 __getattribute__(很少):
① 需要拦截"所有"属性访问(包括已存在的)
如:访问日志、访问控制、完全的代理
② __getattr__ 只能兜底缺失的,管不了已存在的
__getattribute__ vs __getattr__:
__getattribute__:每次都调(所有属性)、危险、罕用
__getattr__:只兜底缺失的、安全、常用
→ 99% 的需求用 __getattr__ 就够,别碰 __getattribute__
所以__getattribute__拦截所有读取(总入口、危险、易递归必须用 super)、罕用
__getattribute__(self, name):每次读取属性都调用(总入口)——拦截「所有」属性访问(不管存不存在)。危险点1:无限递归(最常见坑)——在里面访问 self.something 或 self.__dict__[name] 又会触发 __getattribute__ 无限递归,必须用 super().__getattribute__(name)(或 object.__getattribute__)打破递归。危险点2:影响所有访问 + 性能(每次 obj.x 都调用、性能开销、一个疏忽影响整个对象)。正确用法(罕见):def __getattribute__(self, name): if 拦截条件: ...; return super().__getattribute__(name)。什么时候用(很少):需要拦截「所有」属性访问(包括已存在的、如访问日志/访问控制/完全代理),__getattr__ 只能兜底缺失的管不了已存在的。vs __getattr__:__getattribute__ 每次都调(危险罕用)、__getattr__ 只兜底缺失(安全常用)——99% 的需求用 __getattr__ 就够。理解「getattribute__拦截所有读取(总入口、每次都调);危险:无限递归(内部访问属性又触发、必须用 super)+影响所有+性能;正确用法罕见(拦截所有访问如日志/访问控制);99%用__getattr__就够、别碰__getattribute」,就掌握了 __getattribute__。
四、setattr 与 delattr
理解赋值和删除的拦截:
__setattr__(self, name, value):每次赋值属性都调用
obj.x = v → obj.__setattr__('x', v)
→ 拦截"所有"赋值
无限递归坑(同 __getattribute__):
✗ def __setattr__(self, name, value):
self.name = value # 又触发 __setattr__ → 无限递归!
self.__dict__[name] = value # 这个不递归(直接改字典)
✓ def __setattr__(self, name, value):
super().__setattr__(name, value) # 用 super
# 或 self.__dict__[name] = value # 直接改字典(普通实例)
→ 在里面赋值要用 super 或直接改 __dict__
典型用途:
① 属性校验:
def __setattr__(self, name, value):
if name == "age" and value < 0:
raise ValueError()
super().__setattr__(name, value)
② 只读对象:
def __setattr__(self, name, value):
raise AttributeError("只读")
# 初始化时用 super().__setattr__ 绕过
③ 记录/触发(观察者):
def __setattr__(self, name, value):
super().__setattr__(name, value)
self._notify(name, value) # 属性变化时通知
④ 类型转换/规范化:
def __setattr__(self, name, value):
if name == "price":
value = Decimal(str(value)) # 统一转 Decimal
super().__setattr__(name, value)
__delattr__(self, name):删除属性 del obj.x 时调用
def __delattr__(self, name):
if name in self._protected:
raise AttributeError("不能删除")
super().__delattr__(name)
所以__setattr__拦截所有赋值(校验/只读/记录),内部赋值用 super 或__dict__防递归
__setattr__(self, name, value):每次赋值属性都调用(obj.x = v → obj.__setattr__('x', v)、拦截「所有」赋值)。无限递归坑(同 __getattribute__):在里面 self.name = value 又触发 __setattr__ 无限递归,要用 super().__setattr__(name, value)(或直接改 self.__dict__[name] = value)。典型用途:① 属性校验(if name == "age" and value < 0: raise ValueError)、② 只读对象(raise AttributeError("只读")、初始化时用 super 绕过)、③ 记录/触发(观察者)(赋值后 self._notify)、④ 类型转换/规范化(value = Decimal(str(value)))。__delattr__(self, name):删除属性 del obj.x 时调用(可拦截删除)。理解「setattr__拦截所有赋值(obj.x=v);内部赋值要用 super 或改__dict(防无限递归);用途校验/只读/记录观察者/类型转换;__delattr__拦截删除」,就掌握了赋值和删除的拦截。
五、和 property、描述符的关系
理解这些钩子和 property/描述符的分工:
控制属性的几种机制(对比):
① property:控制"单个"具名属性
class C:
@property
def x(self): ... # 只管 x 这一个属性
@x.setter
def x(self, v): ...
→ 精确控制某个属性的读写(推荐优先用)
② 描述符:可复用的属性控制(类级别)
把属性逻辑封装成描述符类,多个属性/类复用
→ property 其实就是一种描述符
③ __getattr__/__setattr__:控制"所有/动态"属性
→ 不知道属性名(动态)、或要统一处理所有属性时用
→ property/描述符要"预先定义"属性名,这俩不用
什么时候用哪个:
知道具体属性名、逻辑固定 → property(清晰、推荐)
逻辑要在多处复用 → 描述符
属性是动态的(名字不定)→ __getattr__
要统一处理所有赋值(校验/只读)→ __setattr__
代理/转发到另一个对象 → __getattr__
优先级(查找时):
数据描述符 > 实例字典 > 非数据描述符/类属性 > __getattr__
→ property(数据描述符)优先于实例字典和 __getattr__
→ 已有 property 的属性不会走 __getattr__
配合使用:
property 管已知属性 + __getattr__ 兜底未知属性
→ 常见于灵活的配置/代理对象
所以 property 控单个属性(优先)、描述符可复用、__getattr__/__setattr__控动态/所有属性
控制属性的几种机制(对比):① property:控制「单个」具名属性(@property def x 只管 x、精确控制某属性的读写、推荐优先用);② 描述符:可复用的属性控制(类级别)(把属性逻辑封装成描述符类、多个属性/类复用、property 其实就是一种描述符);③ __getattr__/__setattr__:控制「所有/动态」属性(不知道属性名或要统一处理所有属性时用、property/描述符要预先定义属性名而这俩不用)。什么时候用哪个:知道具体属性名逻辑固定用 property、逻辑多处复用用描述符、属性动态用 __getattr__、统一处理所有赋值用 __setattr__、代理转发用 __getattr__。优先级(查找时):数据描述符 > 实例字典 > 非数据描述符/类属性 > __getattr__(property 优先于实例字典和 __getattr__、已有 property 的属性不会走 __getattr__)。配合使用:property 管已知属性 + __getattr__ 兜底未知属性。理解「property 控单个具名属性(优先推荐)、描述符可复用、getattr/setattr__控动态/所有属性;知道名字用 property、动态用__getattr、统一赋值用__setattr__;优先级数据描述符>实例字典>getattr」,就掌握了这些机制的分工。
六、总结与实践
总结属性访问钩子:
四个钩子:
__getattribute__(self, name) 每次读取(总入口、危险、罕用)
__getattr__(self, name) 找不到时兜底(安全、常用)
__setattr__(self, name, val) 每次赋值(校验/只读、较常用)
__delattr__(self, name) 删除时(少用)
调用关系:
obj.x → __getattribute__(正常查找)
→ 失败 → __getattr__(兜底)
防递归(关键):
__getattribute__/__setattr__ 里访问/赋值属性 → 用 super(或 __dict__)
否则无限递归
典型用途:
__getattr__:动态属性、默认值、代理转发、惰性
__setattr__:校验、只读、观察者、类型转换
__getattribute__:拦截所有访问(日志/访问控制,罕用)
选择:
知道属性名 → property(优先,清晰)
动态/未知属性 → __getattr__
统一处理赋值 → __setattr__
拦截所有读取 → __getattribute__(万不得已)
实践建议:
① 99% 用 __getattr__(兜底)+ __setattr__(校验)
② 几乎不碰 __getattribute__(危险)
③ __getattr__ 找不到 raise AttributeError(别返回 None)
④ 内部赋值/访问用 super 防递归
⑤ 优先 property(具名属性)
核心总结:
__getattribute__ 拦所有读取(总入口、危险)
__getattr__ 只兜底缺失的(安全常用)
__setattr__ 拦所有赋值(内部赋值用 super 防递归)
所以__getattribute__拦所有读(危险)、__getattr__兜底缺失(常用)、__setattr__拦所有赋值(防递归)
四个钩子:__getattribute__(每次读取、总入口、危险罕用)、__getattr__(找不到时兜底、安全常用)、__setattr__(每次赋值、校验/只读、较常用)、__delattr__(删除时、少用)。调用关系:obj.x → __getattribute__(正常查找)→ 失败 → __getattr__(兜底)。防递归(关键):__getattribute__/__setattr__ 里访问/赋值属性用 super(或 __dict__)否则无限递归。典型用途:__getattr__(动态属性/默认值/代理转发/惰性)、__setattr__(校验/只读/观察者/类型转换)、__getattribute__(拦截所有访问、罕用)。选择:知道属性名用 property、动态用 __getattr__、统一赋值用 __setattr__。理解「__getattribute__拦所有读取(危险罕用)、__getattr__兜底缺失(安全常用)、setattr__拦所有赋值(内部用 super 防递归)、delattr__删除;99%用__getattr+setattr、别碰__getattribute、优先 property」,就掌握了总结与实践。
记忆钩子:「Python 属性访问钩子(魔术方法):★最容易混的两个读取钩子:①__getattribute__(self,name)——每次访问属性 obj.x 都会调用(不管属性存不存在),是属性查找的『总入口』,重写它风险大(容易无限递归、影响所有访问、有性能开销),罕用②__getattr__(self,name)——只在『正常查找失败(属性不存在)』时才作为『兜底』被调用,是『找不到时怎么办』的钩子,安全常用;★调用关系:obj.x 先调__getattribute__做正常查找(找实例字典/类/父类)→找到就返回、找不到(AttributeError)→才调__getattr__兜底;所以__getattr__只处理『缺失的属性』、对已存在的完全不干预(安全);setattr(self,name,value)——每次 obj.x=v 都调用(拦截所有赋值,用于校验/只读/观察者),delattr__拦删除;★关键坑:在__getattribute/_setattr__里面访问或赋值属性会再次触发它们(无限递归!),必须用 super().getattribute(name)/super().setattr(name,value)或直接改 self.dict[name]打破递归;典型用途:getattr__做动态属性/默认值/代理转发(return getattr(self.wrapped,name))/惰性计算、setattr__做校验/只读/类型转换;实践:99%场景用__getattr(兜底)+setattr(校验)、几乎不碰__getattribute、知道具体属性名优先用 property(数据描述符,优先级高于实例字典和__getattr)」。
七、常见误区与追问
- 误区:getattr 会在每次访问属性时被调用。 不会——
__getattr__只在「正常的属性查找失败(属性不存在于实例字典、类、父类中)」时才作为兜底被调用;对已经存在的属性,访问它完全不会触发__getattr__;真正「每次访问都被调用」的是__getattribute__(属性查找的总入口);所以__getattr__安全、只影响「缺失的属性」,而__getattribute__危险、影响所有访问。 - 误区:getattr 和 getattribute 是一回事,只是拼写不同。 完全不同——
__getattribute__是「所有属性读取的总入口」,obj.x无论 x 存不存在都先经过它、每次都调用,重写它极易无限递归、影响全部访问、罕用;__getattr__是「兜底」,只有当__getattribute__的正常查找失败(抛 AttributeError)时才被调用,只处理不存在的属性、安全常用;调用顺序是:先__getattribute__做正常查找、失败了才轮到__getattr__。 - 误区:在 setattr 里用 self.name = value 来保存属性。 会无限递归——
self.name = value本身又是一次属性赋值、会再次触发__setattr__、如此循环直到 RecursionError;正确做法是用super().__setattr__(name, value)(委托给父类/object 的默认赋值逻辑)或直接操作实例字典self.__dict__[name] = value(绕过__setattr__);同理__getattribute__里访问属性要用super().__getattribute__(name)、__delattr__里删除要用super().__delattr__(name)。 - 误区:getattr 里属性找不到时可以直接返回 None。 不推荐——如果属性确实不存在,
__getattr__应该raise AttributeError(name),以保持正常的属性语义(让hasattr正确返回 False、让getattr(obj, x, default)的默认值机制生效、让拼写错误能被发现);如果静默返回 None,会掩盖 bug(访问一个拼错的属性名不报错而是得到 None)、破坏hasattr和 getattr 默认值的行为;只有当「所有属性名都应该有值」的特殊设计(如某些动态配置)才考虑返回默认值、但也要谨慎。 - 追问:obj.x 这个属性访问,Python 底层的完整查找流程是怎样的? 大致是:① Python 调用
type(obj).__getattribute__(obj, 'x')——这是总入口、每次访问都走;②__getattribute__(默认实现,来自 object)按优先级查找:先看类(及父类,走 MRO)里有没有名为 x 的「数据描述符」(同时有__get__和__set__/__delete__,如 property),有就调用它的__get__;否则看实例字典obj.__dict__里有没有 x,有就返回;否则再看类里有没有 x(普通类属性或「非数据描述符」如普通方法),有就返回(描述符则调__get__);③ 如果以上都没找到,__getattribute__抛 AttributeError;④ 这个 AttributeError 会触发__getattr__(obj, 'x')(如果类定义了__getattr__)——这是兜底、只在正常查找失败时调用;⑤ 如果__getattr__也找不到、就 raise AttributeError、最终抛给调用方;所以优先级是「数据描述符 > 实例字典 > 非数据描述符/类属性 >__getattr__兜底」。 - 追问:什么时候该用 getattr,什么时候该用 property? 用 property 的场景:你「知道具体的属性名」、逻辑相对固定,想精确控制某个(或某几个)具名属性的读写——比如
temperature属性读取时做单位转换、赋值时做范围校验,用@property+@x.setter清晰直观、且是「数据描述符」优先级高、可读性好、IDE 也能识别;用__getattr__的场景:属性是「动态的、名字不确定的」,或者你想「统一兜底所有未定义的属性」——比如实现一个代理对象(把自己没有的属性/方法都getattr转发给内部包装的对象)、一个从字典/数据库动态取值的配置对象(__getattr__里查后端)、或惰性属性;简单说:属性名已知且固定用 property(精确、优先),属性名动态或要兜底未知属性用__getattr__;两者也常配合——property 管已知的核心属性、__getattr__兜底其余动态属性。 - 追问:getattribute 为什么很少用、用它要注意什么? 很少用是因为它「拦截每一次属性访问」、风险和成本都高:① 极易无限递归——在
__getattribute__内部只要访问self的任何属性(包括self.__dict__、self.某方法),都会再次触发__getattribute__、导致无限递归,必须小心地用super().__getattribute__(name)或object.__getattribute__(self, name)来做实际查找;② 影响所有属性——一个疏忽就会破坏整个对象的所有属性访问(包括内部方法调用),bug 面很大;③ 性能开销——每次访问都多一层 Python 函数调用;所以只有在确实需要「拦截包括已存在属性在内的所有读取」时才用它(如全面的访问日志、访问控制、彻底的代理),且实现里必须把实际查找委托给super().__getattribute__();绝大多数「拦截属性」的需求(兜底缺失属性、校验赋值)用__getattr__和__setattr__就够了、更安全,应该优先考虑它们。
八、加强记忆
Python 属性访问钩子(魔术方法):最容易混的两个读取钩子——① __getattribute__(self, name):每次访问属性 obj.x 都会调用(不管属性存不存在),是属性查找的「总入口」,重写风险大(容易无限递归、影响所有访问、有性能开销)、罕用;② __getattr__(self, name):只在「正常查找失败(属性不存在)」时才作为「兜底」被调用,是「找不到时怎么办」的钩子、安全常用。调用关系:obj.x 先调 __getattribute__ 做正常查找(找实例字典/类/父类)→ 找到就返回、找不到(AttributeError)→ 才调 __getattr__ 兜底;所以 __getattr__ 只处理「缺失的属性」、对已存在的完全不干预。__setattr__(self, name, value)——每次 obj.x = v 都调用(拦截所有赋值、用于校验/只读/观察者),__delattr__ 拦删除。关键坑:在 __getattribute__/__setattr__ 里面访问或赋值属性会再次触发它们(无限递归!),必须用 super().__getattribute__(name)/super().__setattr__(name, value) 或直接改 self.__dict__[name] 打破递归。典型用途:__getattr__ 做动态属性/默认值/代理转发(getattr(self._wrapped, name))/惰性、__setattr__ 做校验/只读/类型转换。实践:99% 场景用 __getattr__(兜底)+ __setattr__(校验)、几乎不碰 __getattribute__、知道具体属性名优先用 property(数据描述符、优先级高于实例字典和 __getattr__)。一句话「__getattribute__拦所有读取(总入口、每次都调、危险易递归、罕用)、__getattr__只兜底找不到的属性(安全常用)、__setattr__拦所有赋值(内部赋值用 super 或__dict__防递归);__getattr__做动态属性/代理转发、setattr__做校验/只读;99%用__getattr+setattr、优先 property」。