父类工厂方法返回子类实例,Self类型注解为何遭Mypy报错?
问题分析与解决方案
你的问题核心是对Self类型的理解出现了偏差,这不是Mypy或typing_extensions的问题。
为什么Self会报错?
Self类型的本质是调用该类方法的那个具体类的实例类型,而不是父类Parent的类型。举个例子:
- 如果调用
ChildA.make(1),Self代表的是ChildA类型,但你的方法可能返回ChildB,这就和Self要求的类型不匹配; - 同理,调用
ChildB.make(-1)时,Self是ChildB类型,但方法可能返回ChildA,同样不符合类型要求。
Mypy正是因为检测到这种潜在的类型不匹配,才抛出了报错。
为什么直接赋值给Parent类型没问题?
你提到的这段代码:
param = 1 foo: Parent = ChildA() if param > 0 else ChildB()
是典型的父类类型接收子类实例,这符合Python的子类型赋值规则——所有子类都是Parent的子类型,因此Mypy允许这种赋值。但这和Self的逻辑完全不同:Parent是所有子类的通用父类型,而Self是具体的调用类类型,二者不能划等号。
正确的类型注解方案
直接使用
Parent作为返回类型(你的临时方案其实是合理的)
因为你的工厂方法返回的是Parent的任意子类实例,用父类型Parent作为返回注解完全准确,也最简洁:class Parent: @classmethod def make(cls, param: int) -> Parent: if param > 0: return ChildA() else: return ChildB() class ChildA(Parent): pass class ChildB(Parent): pass用
TypeVar实现更灵活的类型绑定(可选)
如果想避免重复写Parent类名,可以结合TypeVar和bound参数,但本质上和用Parent做返回类型效果一致:from typing_extensions import TypeVar P = TypeVar('P', bound='Parent') class Parent: @classmethod def make(cls: type[P], param: int) -> Parent: if param > 0: return ChildA() else: return ChildB() class ChildA(Parent): pass class ChildB(Parent): pass
补充:什么时候该用Self?
Self适合工厂方法返回当前调用类的实例的场景,比如根据参数创建调用类本身的对象:
from typing_extensions import Self class Parent: def __init__(self, param: int): self.param = param @classmethod def make(cls, param: int) -> Self: return cls(param) # 返回当前调用类的实例,此时Self是正确注解 class ChildA(Parent): pass class ChildB(Parent): pass
这时调用ChildA.make(1)会返回ChildA实例,ChildB.make(-1)返回ChildB实例,完全符合Self的类型要求,Mypy也不会报错。
内容的提问来源于stack exchange,提问作者Florian Brucker
相关产品推荐
相关产品推荐

