__getattr__ 不是万能代理入口,仅在属性查找完全失败时触发,无法捕获已定义的属性、方法及特殊方法(如__len__、__getitem__),容器操作和协议方法必须显式实现。

为什么 __getattr__ 不是万能的代理入口
__getattr__ 只在属性查找失败时触发,也就是说,如果类本身定义了同名属性、方法,或者继承链上有该属性,它根本不会被调用。想靠它“兜底转发”所有访问,会漏掉 __init__、__str__、__len__ 这类特殊方法,甚至普通实例变量——只要你在 __init__ 里赋过值,后续访问就绕过 __getattr__ 了。
常见错误现象:obj.data 转发成功,但 obj.append() 报 AttributeError,因为内部对象有 append 方法,而代理类没显式暴露,又没走 __getattr__(可能你误以为它总触发)。
- 仅当目标属性在实例字典、类、父类中都查不到时,
__getattr__才执行 - 内置方法(如
__iter__、__bool__)默认不通过__getattr__查找,必须显式实现或用__getattribute__ - 若内部对象是
list或dict,别指望obj[0]或obj.keys()自动转发——下标和方法调用是不同机制
正确代理的关键:区分访问类型 + 显式覆盖高频接口
真正可靠的代理,得组合使用 __getattr__ + 少量关键特殊方法重写。比如要代理一个 list,至少得补上 __len__、__getitem__、__setitem__、__iter__,否则用户一用索引或 for 循环就崩。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 先明确内部对象类型(
list、dict、自定义类),再决定要透出哪些协议方法 -
__getattr__专注转发“非协议”的方法调用,例如obj.sort()、obj.get_user() - 对容器行为,必须单独实现
__len__、__contains__等,不能依赖__getattr__ - 如果内部对象可能为
None,在__getattr__开头加空值检查,避免AttributeError混淆成“属性不存在”
示例(代理 list):
class ListProxy:
def __init__(self, data=None):
self._data = data or []
<pre class="brush:php;toolbar:false;">def __len__(self):
return len(self._data)
def __getitem__(self, i):
return self._data[i]
def __getattr__(self, name):
# 防止无限递归:不代理私有属性和已存在的方法
if name.startswith('_'):
raise AttributeError(f"'{self.__class__.__name__}' has no attribute '{name}'")
return getattr(self._data, name)
__getattr__ 转发时容易忽略的细节
直接 return getattr(self._data, name) 看似简单,但有三个典型坑:
- 返回的如果是方法,绑定的是
self._data,不是代理实例——这通常没问题;但如果方法内部检查self.__class__或用isinstance(self, ...),行为就可能异常 - 没处理
__call__:若内部对象本身可调用(如函数或 callable 实例),obj()不会进__getattr__,得额外实现__call__ - 性能隐患:每次访问都走一次
getattr动态查找,比直接访问慢;高频属性(如__class__、__dict__)建议在__getattr__开头快速拦截并抛错,避免无谓查找
更稳妥的 __getattr__ 写法:
def __getattr__(self, name):
if name in ('__class__', '__dict__', '__weakref__'):
raise AttributeError(...)
try:
return getattr(self._data, name)
except AttributeError:
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
什么时候该放弃 __getattr__ 改用 __getattribute__
只有当你需要拦截**所有**属性访问(包括已存在的、私有属性、特殊方法),才考虑 __getattribute__。但它极难写对:一旦出错(比如忘了调用 super().__getattribute__),整个实例就不可用了,连 self._data 都取不到。
真实场景中,95% 的代理需求用 __getattr__ + 显式协议方法就够了。硬上 __getattribute__ 的代价是维护成本陡增,且容易引入难以调试的递归错误。
如果你发现要代理的对象行为特别复杂(比如要统一日志、权限检查、懒加载),与其死磕魔法方法,不如用 types.SimpleNamespace 或 dataclasses 构建轻量包装器,再手动委托关键接口——清晰比“全自动”更重要。


















