Python 类型:处理具有继承的泛型类中的 Type[T] -> T 方法,其中 T 是泛型本身

问题描述 投票:0回答:1

我正在尝试实现抽象泛型类并在子类中细化泛型方法参数/返回值,并且我从 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")

python python-3.x generics python-typing
1个回答
0
投票

答案非常简单 - 我希望返回容器是协变的,而 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
© www.soinside.com 2019 - 2024. All rights reserved.