我正在尝试实现抽象泛型类并在子类中细化泛型方法参数/返回值,并且我从 MyPY 得到了一个奇怪的类型错误提示。
如果有人能解释我是否错了,我将不胜感激。
我有一个用于类型绑定的简单类型层次结构:
class BaseCredentials(ABC):
pass
class StringCredentials(BaseCredentials):
pass
class UserPassCredentials(BaseCredentials):
pass
CredsType = TypeVar("CredsType", bound=BaseCredentials)
CredsTypeCoV = TypeVar(
"CredsTypeCoV",
bound=BaseCredentials,
covariant=True
)
以及绑定到该类型的通用类层次结构:
class RawCredentialsEntity(ABC, Generic[CredsTypeCoV]):
@abstractmethod
def parse(
self, clazz: Optional[Type[BaseCredentials]] = None
) -> CredsTypeCoV:
raise NotImplementedError("Must be implemented!")
class EnvCredentialsEntity(
RawCredentialsEntity[CredsTypeCoV], Generic[CredsTypeCoV],
):
some_var_common_for_subtype: Any
class TextEnvCredentialsEntity(
EnvCredentialsEntity[StringCredentials]
):
def parse(
self, clazz: Optional[Type[BaseCredentials]] = None
) -> StringCredentials:
# logic
return StringCredentials("some id", "some value")
class UserPassEnvCredentialsEntity(
EnvCredentialsEntity[UserPassCredentials]
):
def parse(
self, clazz: Optional[Type[BaseCredentials]] = None
) -> UserPassCredentials:
# logic
return UserPassCredentials("id", "user", "password")
RawEntityType = TypeVar(
"RawEntityType", bound=RawCredentialsEntity[BaseCredentials],
)
这种层次结构的动机:
BaseCredentials
和RawCredentialsEntity
是类似接口的基本类型,它们的额外子类型需要以插件风格实现。
clazz
参数仅用于业务逻辑目的,它可以是 BaseCredentials
的任何子类型,与子类型通用类型无关。
以下是如何使用此层次结构:
class BaseCredentialsSource(ABC, Generic[RawEntityType]):
@abstractmethod
def load_all(self) -> List[RawEntityType]:
raise NotImplementedError("Must be implemented!")
@abstractmethod
def load(
self, credentials_id: str,
clazz: Type[RawEntityType]
) -> RawEntityType:
raise NotImplementedError("Must be implemented!")
class EnvCredentialsSource(
BaseCredentialsSource[EnvCredentialsEntity[BaseCredentials]]
):
def load_all(self) -> List[EnvCredentialsEntity[BaseCredentials]]:
return []
def load(
self, credentials_id: str,
clazz: Type[EnvCredentialsEntity[CredsTypeCoV]]
) -> EnvCredentialsEntity[CredsTypeCoV]:
pass
以下使用示例未通过验证:
service = EnvCredentialsSource()
all_ent: List[EnvCredentialsEntity[BaseCredentials]] = service.load_all() # passes as expected
ent1: UserPassEnvCredentialsEntity = service.load("test_id", TextEnvCredentialsEntity) # expected to fail, I want strict Type[T] -> T where T is bounded to EnvCredentialsEntity
ent2: UserPassEnvCredentialsEntity = service.load("test_id", UserPassEnvCredentialsEntity) # fails, I wonder why
失败错误为:
Incompatible types in assignment (expression has type "EnvCredentialsEntity[UserPassCredentials]", variable has type "UserPassEnvCredentialsEntity")
答案非常简单 - 我希望返回容器是协变的,而 List 不是,因为它是可变的。在这种情况下就需要使用序列和映射。
我重写了代码,这样让 pypy 高兴:
class RawCredentialsEntity(ABC, Generic[CredsTypeCoV]):
@abstractmethod
def parse(
self, clazz: Optional[Type[CredsTypeCoV]] = None
) -> CredsTypeCoV:
raise NotImplementedError("Must be implemented!")
class EnvCredentialsEntity(
RawCredentialsEntity[CredsTypeCoV], Generic[CredsTypeCoV],
):
pass
class BaseCredentialsSource(ABC):
@abstractmethod
def load_all(self) -> Sequence[
RawCredentialsEntity[CredsType]
]:
raise NotImplementedError("Must be implemented!")
@abstractmethod
def load(
self, credentials_id: str,
clazz: Type[RawEntityType]
) -> RawEntityType:
raise NotImplementedError("Must be implemented!")
class TextEnvCredentialsEntity(
EnvCredentialsEntity[StringCredentials]
):
def parse(
self, clazz: Optional[Type[BaseCredentials]] = None
) -> StringCredentials:
...
return StringCredentials(credentials_id, value)
class EnvCredentialsSource(BaseCredentialsSource):
def load_all(
self
) -> Sequence[EnvCredentialsEntity[CredsType]]:
return []
def load(
self, credentials_id: str,
clazz: Type[RawEntityType]
) -> RawEntityType:
pass