__getattr__仅在访问不存在的属性时触发;动态代理需用getattr(self._target, name)转发,但魔术方法需手动实现,且须防无限递归、内置属性异常及类型匹配问题。

__getattr__ 什么时候会被触发?
它只在访问对象不存在的属性时才被调用,不是每次属性访问都走这里。比如 obj.x 中 x 在 obj.__dict__ 或其类继承链里找不到,才会进入 __getattr__。如果属性存在,连 __getattribute__ 都不会绕过 —— 所以别指望用它拦截所有访问,更不能靠它做通用属性劫持。
动态代理的核心逻辑怎么写?
典型做法是把目标对象存为实例变量(如 self._target),然后在 __getattr__ 里查它、转发调用。但要注意:方法绑定、返回值类型、self 绑定这些细节容易出错。
- 直接
return getattr(self._target, name)可以拿到属性或未绑定方法,但调用时可能报TypeError: missing 1 required positional argument: 'self' - 正确做法是让 Python 自动绑定:用
getattr(self._target, name)拿到结果后,直接返回,让调用方正常调用 —— 方法对象本身已包含绑定信息(如果是实例方法) - 如果
_target是类实例,且你返回的是它的方法,那没问题;但如果_target是函数或非实例对象,就得额外处理
示例:
class Proxy:
def __init__(self, target):
self._target = target
<pre class="brush:php;toolbar:false;">def __getattr__(self, name):
return getattr(self._target, name)使用
class RealObj: def say(self, msg): return f"Real: {msg}"
p = Proxy(RealObj()) print(p.say("hello")) # ✅ 输出 "Real: hello"
为什么不能用 __getattr__ 代理魔术方法?
Python 对双下划线开头结尾的方法(如 __len__、__str__、__call__)有特殊查找逻辑:它们**不会**触发 __getattr__。即使你在代理类里没定义 __len__,解释器也不会去调 __getattr__ 查 _target.__len__。
- 想支持
len(proxy),必须显式实现__len__(self)并转发到self._target - 同理,
str(proxy)、proxy()、proxy[0]等都需要各自手动实现对应魔术方法 - 这不是缺陷,是设计使然:魔术方法的解析优先级高于
__getattr__
实际用时最容易漏掉什么?
三个关键点常被跳过:
- 没检查
_target是否真有那个属性,直接getattr(...)会抛AttributeError,而__getattr__里再抛一次就变成无限递归(因为异常触发属性访问失败又进自己)—— 必须用hasattr(self._target, name)或getattr(self._target, name, _sentinel)做兜底 - 忽略对
__dict__、__class__等内置属性的特殊处理,导致vars(proxy)返回空或出错 - 把代理对象传给依赖
isinstance或type()的库时,类型不匹配,需要额外实现__class__或用__instancecheck__(但后者只能在元类里设)
真正可靠的动态代理,往往得配合 __getattribute__ 或 types.MethodType 修补,或者直接用 functools.wraps 包装,而不是只靠 __getattr__。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











