使用类装饰器装饰__init__时触发MyPy 'Unsupported decorated constructor type'错误的问题咨询
__init__时触发MyPy 'Unsupported decorated constructor type'错误的问题咨询 我最近尝试用类形式的装饰器来装饰另一个类的方法,但当把这个装饰器应用到__init__构造函数上时,MyPy抛出了Unsupported decorated constructor type的错误,同时在实例化类时还出现了Returning Any from function declared to return "Foo"的问题。我想搞清楚这是我的代码逻辑有问题,还是MyPy本身的限制/ bug?
以下是我的代码:
from types import MethodType from typing import TypeVar, Callable, Type, ParamSpec, Generic T = TypeVar("T") P = ParamSpec("P") R = TypeVar("R") class method_decorator(Generic[P, R]): def __init__(self, method: Callable[P, R]): self.method = method def __get__(self, instance: T, cls: Type[T]) -> Callable[..., R]: return MethodType(self, instance) def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R: return self.method(*args, **kwargs) class Foo: @method_decorator # error: Unsupported decorated constructor type [misc] def __init__(self, x: int): self.x = x @method_decorator def mul(self, a: int) -> int: return self.x * a def gen_foo(x: int) -> Foo: return Foo(x) # error: Returning Any from function declared to return "Foo" [no-any-return] foo = gen_foo(2) print(foo.mul(3))
用MyPy v1.13.0启用--strict模式检查的结果:
$ mypy --strict test.py test.py:19: error: Unsupported decorated constructor type [misc] test.py:28: error: Returning Any from function declared to return "Foo" [no-any-return] Found 2 errors in 1 file (checked 1 source file)
问题分析
这其实不是你的代码运行时的错误,而是MyPy对构造函数__init__的装饰有特殊限制:
MyPy目前不支持用实现了描述符协议(即包含__get__方法)的类装饰器来装饰__init__。因为构造函数的调用逻辑和普通实例方法不同,MyPy无法正确推断被描述符装饰后的构造函数类型,进而导致后续实例化Foo时被推断为Any,触发第二个返回类型错误。
解决方案
你可以根据需求选择以下几种方案:
方案1:改用函数式装饰器装饰__init__
函数式装饰器是MyPy完全支持的构造函数装饰方式,你可以为__init__单独使用函数式装饰器,普通方法依然保留类装饰器:
from types import MethodType from typing import TypeVar, Callable, Type, ParamSpec, Generic T = TypeVar("T") P = ParamSpec("P") R = TypeVar("R") class method_decorator(Generic[P, R]): def __init__(self, method: Callable[P, R]): self.method = method def __get__(self, instance: T, cls: Type[T]) -> Callable[..., R]: return MethodType(self, instance) def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R: return self.method(*args, **kwargs) # 新增函数式装饰器 def func_decorator(func: Callable[P, R]) -> Callable[P, R]: def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: return func(*args, **kwargs) return wrapper class Foo: @func_decorator def __init__(self, x: int): self.x = x @method_decorator def mul(self, a: int) -> int: return self.x * a def gen_foo(x: int) -> Foo: return Foo(x) # 现在MyPy可以正确推断类型 foo = gen_foo(2) print(foo.mul(3))
方案2:忽略MyPy的构造函数装饰错误
如果你想继续使用类装饰器,可以通过# type: ignore[misc]抑制__init__的装饰错误,MyPy就能正确推断后续的实例化类型:
class Foo: @method_decorator # type: ignore[misc] def __init__(self, x: int): self.x = x @method_decorator def mul(self, a: int) -> int: return self.x * a def gen_foo(x: int) -> Foo: return Foo(x) # 错误消失
方案3:调整类装饰器的类型注解
你可以修改类装饰器的__get__方法返回类型,让MyPy能更好地识别构造函数的类型(需要添加一个类型忽略来绕过MethodType的类型不匹配):
class method_decorator(Generic[P, R]): def __init__(self, method: Callable[P, R]): self.method = method def __get__(self, instance: T, cls: Type[T]) -> Callable[P, R]: return MethodType(self, instance) # type: ignore[return-value] def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R: return self.method(*args, **kwargs)
这种方式可以让MyPy接受对__init__的装饰,同时保留类装饰器的功能。
备注:内容来源于stack exchange,提问作者s417-lama

