代理通过持有真实对象引用并重载___getattr__实现轻量化动态拦截,在代理层统一制作权限验证和审计日志,需要对特殊方法进行显式处理,对保证日志完整性进行异常捕捉,接受类型/序列化兼容性成本。如何拦截和控制代理,调用原方法
核心是让代理对象持有真实对象并引用它 __getattr__ 或者在显式定义的方法中进行前置检查。请勿重写所有方法-使用 __getattr__ 动态代理更轻,避免错过新方法。
常见错误:直接继承原类或用装饰器装饰类本身,绕过代理逻辑;或无条件调用代理 super().__getattr__(name),导致递归崩溃。
- 代理不继承目标类,只持有一个
self._real_obj - 重载
__getattr__,在内部做出权限/审计判断,然后通过getattr(self._real_obj, name)获取真实的方法 - 若需拦截
__init__或特殊方法(如__str__),必须实现显式,__getattr__它们不会被捕获
审计日志和权限判断不能耦合在业务方法中,必须接收到代理。关键是将验证逻辑拉入可插入的钩子中,如 check_access(method_name, *args, **kwargs) 和 log_call(method_name, args, result, duration)。
容易踩的坑:在 __getattr__ 直接执行方法并记录返回值,但不处理异常——一旦目标方法错误,审计日志中途断裂;其他人将用户身份信息编码到代理类别,导致无法重复使用。
立即学习“Python免费学习笔记(深入);
Python数据分析助手
为业务和科研数据的快速处理提供Python数据清理、统计分析和可视化建议。
下载- 用
try/except调用包装的真实方法,确保包装的真实方法得到保证log_call总是被执行(无论是成功还是失败) - 应接收权限检查函数
self._user_context(在代理初始化时引入),而不是从全局或线程变量读取 - 审计字段至少包括:时间戳、方法名称、参数(脱敏后)、返回值的类型,耗时,是否异常
functools.wraps 而且要手动代理
functools.wraps 只解决函数装饰的元信息传输问题,代理对类方法无效——它不改变调用链,也不提供实例级(如当前用户和租户ID)。代理模式的核心价值在于实例生命周期中的维持状态,例如 AdminProxy(user=alice) 例子总是代表的 alice 操作上下文。
经常高估性能影响:CPython 下 __getattr__ 费用约 50–100ns远低于一次数据库查询或 HTTP 请求;但如果代理逻辑中做了同步日志写磁盘或远程鉴定,那才是真正的瓶颈。
-
wraps无法拦截属性访问,无法动态决定是否释放,无法绑定实例状态 - 代理类可以配合
__enter__/__exit__提高临时权限(如with proxy.elevate(): ...) - 想支持异步方法吗?额外实现
__aenter__和代理__await__,wraps完全不涉及这些
检查代理类和原类的类型,序列化,IDE 自动补充会有问题。比如。 isinstance(proxy, OriginalClass) 返回 False,用 dataclasses.asdict() 处理代理实例会报错,Pydantic 模型验证也可能失败。
这不是 bug,这是代理模式的固有成本——你主动切断类型的继承链,以换取控制。接受这一点比强制补丁更安全。
- 需要
isinstance通过?加__class__属性代理(危险,谨慎使用)或改用抽象基类 +register - 序列化失败?重写
__getstate__,只返回self._real_obj状态,或显式定义to_dict() - IDE 填补缺失?在代理类顶部添加类型注释:
def __getattr__(self, name) -> Any: ...,配合typing.TYPE_CHECKING导入原类