Python代理类应优先用__getattr__拦截方法调用,通过弱引用持有原始对象,对可调用属性返回带权限校验和日志的闭包,避免递归、循环引用及误拦数据读取。

代理类怎么拦截原始对象的方法调用
Python里没有语言级代理语法,必须靠 __getattr__ 或 __getattribute__ 拦截属性访问。用 __getattr__ 更安全——它只在属性不存在时触发,不会干扰代理类自身字段;而 __getattribute__ 会拦截所有访问,一不留神就递归报错 RecursionError: maximum recursion depth exceeded。
实操建议:
- 代理类继承自原始类(可选),但更推荐组合:在代理中持有一个
_real_object实例 - 重写
__getattr__,内部调用getattr(self._real_object, name)转发未定义的属性或方法 - 不要重写
__getattribute__,除非你明确需要拦截__init__、_private等所有访问,且已用super().__getattribute__避开递归
权限校验该放在哪里做才不破坏原逻辑
校验不能塞进 __getattr__ 里直接判断方法名——因为用户可能通过 getattr(obj, 'method')() 动态调用,此时你拦不到执行时机。正确做法是:在 __getattr__ 返回前,对方法对象再包一层 callable,把校验逻辑“延迟”到真正调用时执行。
实操建议:
- 当
name对应的是可调用对象(callable(attr)),返回一个闭包函数,内含权限检查 + 原方法调用 - 校验逻辑应依赖外部上下文,比如从线程局部变量
threading.local()或显式传入的user_role参数获取权限信息,避免硬编码 - 对非方法属性(如字段、属性)不做校验,否则会误拦数据读取——权限通常只约束行为,不约束状态可见性
日志记录为什么不能只记方法名
只记录 "calling method_x" 几乎没用。真实排障需要知道谁在什么时候、用什么参数、以什么身份、从哪行代码触发了这次调用。漏掉任一维度,日志就变成噪音。
实操建议:
- 在包装后的 callable 中,用
logging.info记录:method_name、args、kwargs、time.time()、当前threading.current_thread().name - 避免记录敏感参数(如密码、token),可用白名单字段过滤或统一替换为
- 不要在代理初始化时就打日志——那是构造过程,不是业务调用;日志必须紧贴实际方法执行前后
代理类和原始类之间如何避免循环引用
常见错误是代理类在 __init__ 里直接赋值 self._real_object = real_obj,而原始对象又持有代理实例(比如回调注册),导致 gc 无法回收,内存缓慢增长。
实操建议:
- 优先用弱引用:
import weakref,然后存self._real_object = weakref.proxy(real_obj) - 如果原始对象生命周期短于代理,或代理需保证强引用(如缓存场景),则必须由使用者明确管理生命周期,代理类文档里要加粗写明 “caller must ensure real_object outlives proxy”
- 用
sys.getrefcount()快速验证:对原始对象调用前后 refcount 是否异常增加,是发现隐式强引用的最快方式
权限和日志不是加个装饰器就能跑通的事——代理类里每个 __getattr__ 分支、每次 getattr 转发、每处闭包捕获,都得想清楚控制流是否可预测。最常被忽略的,是方法返回值本身也可能需要被代理(比如返回另一个可操作对象),这时候就得递归代理,而不是简单透传。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











