Python装饰器中使用类作为包装器时丢失self的原理与解决方案

7次阅读

Python 装饰器中使用类作为包装器时丢失 self 的原理与解决方案

当用类实现装饰器包装器时,因未实现描述符协议,无法自动绑定实例方法的 `self` 参数,导致调用时需手动传入对象;而函数式装饰器天然支持该机制,是更简洁、可靠的选择。

在 Python 中,实例方法的自动 self 绑定并非语言魔法,而是基于 描述符协议(Descriptor Protocol) 的标准行为。当访问一个类属性(如 obj.method)时,若该属性对象实现了 __get__ 方法,Python 会自动调用它,并传入实例(obj)和所属类(type(obj))。内置函数对象(function)正是通过这一机制,在被访问为实例属性时返回一个 绑定方法(bound method),从而将 self 隐式注入后续调用。

而问题中的 broken_decorator 返回的是一个普通类实例(Wrapper()),尽管它定义了 __call__,却 未实现 __get__,因此不构成非数据描述符(non-data descriptor)。结果就是:three.plus_ 直接返回该 Wrapper 实例本身,而非绑定后的可调用对象;调用 three.plus_(4) 实际等价于 wrapper.__call__(4) —— 此时 self 参数缺失,Python 不会自动补上 three,而是将第一个显式参数 4 错误地当作 my_class_self,造成逻辑混乱(如示例中需传入 MyClass(4) 才“凑效”)。

✅ 正确做法:优先使用闭包函数作为装饰器包装器(如 @decorator):

def decorator(func):     def wrapper(self, y):  # self 自动由绑定机制传入         print(f"Before calling {func.__name__}")         result = func(self, y)         print("After call")         return result     return wrapper

⚠️ 若必须用类封装状态(如计数器、缓存等),应 显式实现描述符协议

立即学习Python 免费学习笔记(深入)”;

class StatefulDecorator:     def __init__(self, func):         self.func = func         self.call_count = 0      def __call__(self, *args, **kwargs):         self.call_count += 1         return self.func(*args, **kwargs)      def __get__(self, obj, objtype=None):         # 当通过实例访问时,返回绑定的代理对象         if obj is None:             return self  # 类访问,返回装饰器自身         # 返回一个能记住 obj 和 self.func 的绑定对象         return lambda *args, **kwargs: self.func(obj, *args, **kwargs)  # 使用示例(此时 three.plus_ 会正确绑定 self=three)@StatefulDecorator def plus_(self, y):     print(f"{self.x} + {y} = {self.x + y}")

? 总结:

  • 函数式装饰器开箱即用,天然支持方法绑定,性能优、代码简;
  • 类装饰器需手动实现 __get__ 才能模拟绑定行为,逻辑复杂且易出错;
  • 除非有强需求(如需跨实例共享状态、动态配置或类型提示增强),否则 避免为装饰器包装器使用无描述符支持的类
  • 调试时可检查 hasattr(decorated_method, ‘__func__’) —— 若为 True,说明已成功绑定;若调用时报 TypeError: missing 1 required positional argument: ‘self’,大概率是包装器未遵循描述符协议。

text=ZqhQzanResources