__getattribute__ 容易触发无限递归,因为其内部访问任何实例属性(如self._cache或self.__dict__)都会再次调用自身;必须统一使用object.__getattribute__(self, name)绕过自定义逻辑,避免getattr(self, name)、self.name等隐式触发方式。

为什么 __getattribute__ 容易触发无限递归
因为每次访问实例的任意属性(包括 self.__dict__、self._cache 这类内部变量),都会再次调用 __getattribute__。如果你在方法里直接写 self.xxx 或 self.__dict__,就等于主动跳进自己的回调里。
必须用 object.__getattribute__ 绕过自定义逻辑
所有安全访问都得委托给父类实现,不能走当前类的重载逻辑。常见错误是误用 getattr(self, name) 或 self.__dict__.get(name) —— 这两个都会再次触发 __getattribute__。
- ✅ 正确:用
object.__getattribute__(self, name) - ❌ 错误:
getattr(self, name)、self.name、self.__dict__、hasattr(self, name) - ⚠️ 特别注意:
super().__getattribute__(name)在多重继承时可能仍被拦截,最稳的是显式调用object.__getattribute__
典型防护写法示例
下面是一个带缓存逻辑但不递归的写法:
class SafeProxy:
def __init__(self):
self._cache = {}
def __getattribute__(self, name):
# 先绕过自定义逻辑,读取内部字段
_cache = object.__getattribute__(self, '_cache')
if name in _cache:
return _cache[name]
# 获取真实值(同样绕过)
try:
value = object.__getattribute__(self, name)
except AttributeError:
raise
# 缓存非特殊属性(避免缓存 __dict__、__class__ 等)
if not name.startswith('__'):
_cache[name] = value
return value
关键点:所有对 self 的属性读取,包括 _cache 和 name 对应的真实值,全部走 object.__getattribute__;缓存逻辑只作用于用户级属性,避开双下划线名。
容易被忽略的隐式调用场景
有些操作表面看不读属性,实际会触发 __getattribute__:
- 打印对象时调用
__repr__或__str__,如果这些方法里用了self.xxx就崩 -
isinstance(obj, cls)不触发,但obj.__class__会触发 - 使用
@property的 getter 会被正常调用,但 property 本身定义在类上,不走实例的__getattribute__;不过 getter 内部若访问其他实例属性,就得小心
最稳妥的习惯:只要在 __getattribute__ 体内,任何对 self 的属性访问,无条件走 object.__getattribute__(self, ...)。










