Python 3.11.2中使用描述符协议绑定类方法报错问题
Python 3.11中跨类绑定方法失效的问题解决
问题场景
为了在新增API语法糖时保持向后兼容,通过描述符协议将Decorator类的方法绑定到SubscriptableFunctionWrapper实例,同时锁定Decorator的self值。这套逻辑在Python 3.7~3.10中正常运行,但在Python 3.11中失效。
核心需求的简化代码如下:
包装函数类
class SubscriptableFunctionWrapper: def __init__(self, fn, decorator_name_variant_method): self.fn = fn self.name_variant = decorator_name_variant_method.__get__(self) def __getitem__(self, keys): return 42 def __call__(self, *args, **kwargs): return self.fn(*args, **kwargs)
装饰器类
class Decorator: def __init__(self, name = "", variant = "default"): self.name = name self.variant = variant def __call__(self, fn): if self.name == "": self.name = fn.__name__ return SubscriptableFunctionWrapper(fn, self.name_variant) def name_variant(self): return (self.name, self.variant)
测试代码
@Decorator() def adds_two(a): return a + 2 assert adds_two(2) == 4 # 调用功能正常 assert adds_two["keys"] == 42 # 下标访问正常 assert adds_two.name_variant() == ("adds_two", "default") # 绑定方法调用失效(3.11中报错)
失效原因
Python 3.11对绑定方法(instancemethod)的内部校验逻辑做了更新:当你通过method.__get__(instance)将一个类的方法绑定到另一个类的实例时,调用该绑定方法会检查调用者的类型是否与方法定义的类匹配。
在你的代码中,name_variant是Decorator类的实例方法,却被绑定到了SubscriptableFunctionWrapper实例。3.11中调用adds_two.name_variant()时,Python会验证调用者(adds_two)是否是Decorator的实例,显然不满足,因此抛出类型错误。
修复方案
以下三种方案均可解决问题,可根据实际场景选择:
方案1:手动包装方法固定self
直接创建一个包装函数,把Decorator实例作为方法的第一个参数传入,绕过描述符的绑定校验:
class SubscriptableFunctionWrapper: def __init__(self, fn, decorator_instance): self.fn = fn # 手动包装,固定Decorator实例作为方法的self def bound_name_variant(): return decorator_instance.name_variant() self.name_variant = bound_name_variant def __getitem__(self, keys): return 42 def __call__(self, *args, **kwargs): return self.fn(*args, **kwargs) # 对应修改Decorator的__call__方法 class Decorator: # ... 其他代码不变 ... def __call__(self, fn): if self.name == "": self.name = fn.__name__ return SubscriptableFunctionWrapper(fn, self)
方案2:用functools.partial绑定参数
借助functools.partial固定方法的第一个参数(即Decorator的实例),更简洁:
from functools import partial class SubscriptableFunctionWrapper: def __init__(self, fn, decorator_instance): self.fn = fn self.name_variant = partial(decorator_instance.name_variant) def __getitem__(self, keys): return 42 def __call__(self, *args, **kwargs): return self.fn(*args, **kwargs) # Decorator的__call__修改同方案1
方案3:动态代理所有Decorator方法
如果需要绑定多个Decorator的方法,用__getattr__实现动态代理更灵活,无需逐个绑定:
class SubscriptableFunctionWrapper: def __init__(self, fn, decorator_instance): self.fn = fn self._decorator = decorator_instance def __getattr__(self, name): # 访问不存在的属性时,从Decorator实例获取并执行 attr = getattr(self._decorator, name) if callable(attr): return lambda *args, **kwargs: attr(*args, **kwargs) return attr def __getitem__(self, keys): return 42 def __call__(self, *args, **kwargs): return self.fn(*args, **kwargs) # Decorator的__call__修改同方案1
验证
修改后的代码在Python 3.11及之前版本均可正常运行,满足所有需求:调用、下标访问、绑定方法返回正确结果。
内容的提问来源于stack exchange,提问作者Erik Eppel
相关产品推荐
相关产品推荐

