__getattr__ 仅在属性确实不存在时触发,它不拦截已定义的实例变量、类变量、方法或property;若误写属性名但该名实际存在,或未显式返回值/抛异常,则行为异常。

__getattr__ 只在属性真实不存在时才触发
__getattr__ 不是万能钩子,它只在 Python 确认对象没有该属性(包括没在 __dict__、父类、描述符中找到)后才调用。如果写错了名字但属性其实存在,比如把 user.name 误写成 user.nam,而对象恰好有 nam 这个属性(哪怕只是拼写巧合),__getattr__ 就不会执行。
常见错误现象:AttributeError 没被捕获,或预期的日志/默认值没出现——大概率是因为属性名拼写正确,或者你误用了 __getattribute__(它每次访问都触发,容易引发递归)。
- 只对真正缺失的属性生效;已定义的实例变量、类变量、方法、property 都绕过它
- 不要在
__getattr__内访问自身其他属性(如self._cache),除非确保它已存在,否则可能再次触发__getattr__导致无限递归 - 若需统一拦截所有属性访问(含存在的),改用
__getattribute__,但必须小心调用super().__getattribute__(name)获取原始值,否则会卡死
返回值决定调用行为,不能只 print
很多人写完 __getattr__ 只加一行 print(f"missing: {name}"),结果发现属性访问依然抛错——因为 __getattr__ 必须显式 return 一个值,或显式 raise AttributeError。Python 不会自动忽略或兜底。
使用场景:提供默认值、动态代理、懒加载字段、兼容旧 API。
立即学习“Python免费学习笔记(深入)”;
- 返回任意值(字符串、函数、计算结果),调用方会收到它,就像属性真存在一样
- 若想维持原错误行为,必须
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'") - 返回函数时注意绑定:直接
return self._make_method(name)是 OK 的;但别写return self._make_method(name)()(立即执行)
典型安全写法:避免递归 + 显式 fallback
下面这个模式能避开大多数坑:
class SafeProxy:
def __init__(self):
self._data = {}
<pre class='brush:python;toolbar:false;'>def __getattr__(self, name):
# 先检查是否是私有属性或特殊方法,直接拒绝(防止意外触发)
if name.startswith('_'):
raise AttributeError(f"'{type(self).__name__}' has no attribute '{name}'")
# 尝试从 _data 动态获取
if name in self._data:
return self._data[name]
# 否则按需生成或抛错
if name == 'status':
return 'active'
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")关键点:
- 用
if name.startswith('_')拦住双下划线属性(如__dict__、__module__),否则vars(obj)这类操作会反复进__getattr__ - 所有分支必须有明确
return或raise,不能漏掉末尾 - 动态逻辑尽量走字典/缓存,而不是每次都重新计算,除非开销极小
和 __getattribute__ 的性能与用途差异
__getattr__ 开销几乎为零(只在缺失时调),__getattribute__ 则每次属性访问都走一遍,慢且危险。90% 的“拦截未定义属性”需求,用 __getattr__ 就够了。
性能影响:在高频循环里频繁访问未知属性(比如解析配置字典),__getattr__ 的额外字典查找或函数调用会累积可观开销;此时应预热常用属性到实例上,或改用 getattr(obj, name, default) 显式处理。
-
__getattr__:适合兜底、兼容、懒初始化 -
__getattribute__:适合审计、日志、强制权限检查(但务必用super().__getattribute__获取原始值) - 两者共存时,
__getattribute__先运行;若它没抛错也没返回,则再看__getattr__
最容易被忽略的是:__getattr__ 对继承链里的属性不敏感——如果父类定义了 foo,子类没覆盖,那访问子类实例的 foo 根本不会进子类的 __getattr__。


















