Pyright类型兼容问题:Self@SubA与SubA不兼容原因咨询
使用Pyright实现类型安全时的泛型类型兼容问题
我在项目里用Pyright做类型检查时碰到了一个搞不懂的类型问题,目前找到两种解决办法:要么给TypeVar加上covariant=True,要么显式指定方法的self参数类型为SubA。但我对问题本身还有不少疑问,先贴出复现代码和报错信息:
问题复现代码
from typing import TypeVar, Callable, Generic, Any class A: ... # 绑定到A及其子类的TypeVar AT = TypeVar("AT", bound=A) # 基于AT的泛型类B class B(Generic[AT]): ... # 将A子类的方法转换为B[AT]实例的装饰器 def to_b_instance(method: Callable[[AT], Any]) -> B[AT]: return B() # 接收B[AT]断言和B[AT]实例的装饰器 def check_b_instance(predicate: Callable[[B[AT]], bool]) -> Callable[[B[AT]], Any]: def inner(b: B[AT]): ... return inner # 检查B[SubA]实例的断言函数 def my_predicate(b: B["SubA"]): return True class SubA(A): @check_b_instance(my_predicate) # <- 报错位置 @to_b_instance def methode(self): pass
报错信息
Argument of type "B[Self@SubA]" cannot be assigned to parameter of type "B[SubA]" "B[Self@SubA]" is incompatible with "B[SubA]" TypeVar "AT@B" is invariant Type "Self@SubA" cannot be assigned to type "SubA" Pyright(reportGeneralTypeIssues)
疑问解答
1. 这个错误没被mypy触发,是Pyright的bug吗?
不是bug。Pyright和mypy在类型推导的严格程度、细节处理上存在差异:
- Pyright对
Self类型的推导更精细,会在实例方法中保留Self类型(代表当前实例的具体类型,可能是子类),而mypy在这里可能直接把Self简化成了SubA。 - 两者的类型兼容性检查逻辑不同,Pyright对不变泛型的匹配要求更严格,因此会触发该错误,而mypy没有。
2. Self@SubA和SubA的类型差异是什么?
SubA是类本身的类型,代表所有SubA类的实例。Self@SubA是自类型,在实例方法中代表当前调用方法的实例的具体类型。如果SubA还有子类(比如SubSubA),那么在SubSubA的实例上调用methode时,Self就代表SubSubA而非SubA。简单来说,Self是和实例绑定的动态类型,比SubA的类型精度更高。
3. 为什么给TypeVar设置协变就能解决问题?
泛型的可变性决定了类型兼容规则:
- 默认情况下
TypeVar是不变的,也就是说B[X]和B[Y]只有在X和Y完全相同时才兼容。 - 给
AT加上covariant=True后,B变成协变泛型类。协变的规则是:如果X是Y的子类,那么B[X]可以赋值给B[Y]。 - 这里
Self@SubA是SubA的子类型(因为Self代表的实例类型可能是SubA或它的子类),所以协变的B[Self@SubA]可以兼容B[SubA],错误也就消失了。
4. 显式指定self类型为SubA能解决问题的原因是什么?
当你显式把self的类型指定为SubA时,Pyright不会再推导self为Self@SubA,而是直接将其认定为SubA类型。这样to_b_instance返回的就是B[SubA],和my_predicate要求的B[SubA]类型完全匹配,自然就不存在兼容性问题了。相当于手动降低了类型的精确性,消除了Self和SubA的类型差异。
内容的提问来源于stack exchange,提问作者CallMePixelMan
相关产品推荐
相关产品推荐

