← 返回题目列表

Python 的 __getattr__、__getattribute__、__setattr__ 有什么区别?

高频 困难 第 12 / 21 题 更新于 2026/07/31
Python__getattr____getattribute__属性拦截

简化版

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.somethingself.__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__

四、setattrdelattr

理解赋值和删除的拦截:

__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 = vobj.__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__ 危险、影响所有访问。
  • 误区:getattrgetattribute 是一回事,只是拼写不同。 完全不同——__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」。