使用ParamSpec与Concatenate为子类函数的装饰器添加类型注解时的MyPy类型兼容问题
使用ParamSpec与Concatenate为子类函数的装饰器添加类型注解时的MyPy类型兼容问题
我完全懂你遇到的困扰——代码跑起来明明没问题,但MyPy就是揪着类型不兼容的报错不放,而且你不想靠断言绕过类型检查,想找到正确的注解方式对吧?
先给你拆解一下报错原因:MyPy这里的严格检查是因为Callable类型的参数是逆变的。你现在的装饰器期望接收一个第一个参数为Parent的Callable,但子类Child的方法第一个参数是Child(虽然它是Parent的子类),但对于Callable来说,接受更具体类型(Child)的函数不能直接被当成接受更泛化类型(Parent)的函数使用——毕竟如果有人拿装饰后的函数去传入一个普通Parent实例,你的子类方法可能依赖Child特有的属性,那就会出问题,所以MyPy会抛出这个不兼容的提示。
那怎么解决这个问题呢?我们只需要新增一个绑定到Parent的TypeVar,让装饰器能适配Parent及其所有子类的self参数。修改后的代码如下:
from typing import ParamSpec, TypeVar, Concatenate from collections.abc import Callable P = ParamSpec("P") T = TypeVar("T") # 新增一个绑定到Parent的TypeVar,用于适配子类的self Self = TypeVar("Self", bound=Parent) class Parent: def parent_function(self, s: str): print("hola " + s) def decorate( func: Callable[Concatenate[Self, P], T] ) -> Callable[Concatenate[Self, P], T]: def wrapper(self: Self, *args: P.args, **kwargs: P.kwargs) -> T: self.parent_function("there") return func(self, *args, **kwargs) return wrapper class Child(Parent): @decorate def child_function(self, s: str): print("hello " + s) child = Child() child.child_function("world")
关键改动说明:
- 新增
Self = TypeVar("Self", bound=Parent):这个TypeVar会被MyPy自动推断为Parent或者它的任意子类,确保装饰器能兼容所有继承自Parent的类的方法。 - 把装饰器参数和返回值里的
Concatenate[Parent, P]替换为Concatenate[Self, P],同时wrapper里的self类型也改为Self:这样当Child类使用装饰器时,Self会被推断为Child,和方法的self类型完全匹配,MyPy就不会再报错了。
这样修改后,代码的运行逻辑完全不变,同时能通过MyPy的类型检查,也不需要用断言来绕过类型系统,完美符合你的需求。
备注:内容来源于stack exchange,提问作者AndyMac
相关产品推荐
相关产品推荐

