
当用类实现装饰器包装器时,因未实现描述符协议,无法自动绑定实例方法的 `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’,大概率是包装器未遵循描述符协议。






























