Python 3 中抽象基类的工厂类方法的类型提示

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

我有两个抽象类,

AbstractA
AbstractB
AbstractB
是通用的,其类型参数绑定到
AbstractA
AbstractB
还有一个工厂类方法,它返回其子类之一的实例——哪个子类是根据某个输入参数确定的。请参阅下面的最小示例。请注意,经过反复试验,我发现需要在
B_type
中添加
AbstractB.factory()
的类型提示。

最小示例

from __future__ import annotations
from abc import ABC
from typing import Generic, TypeVar, Type, Any


class AbstractA(ABC):
    pass


class ConcreteA1(AbstractA):
    pass


class ConcreteA2(AbstractA):
    pass


ATypeT = TypeVar("ATypeT", bound=AbstractA)


class AbstractB(ABC, Generic[ATypeT]):

    @classmethod
    def factory(cls, typeB_selector: int) -> AbstractB[Any]: # which type hint here?

        B_type: Type[AbstractB[Any]] # which type hint here?
        if typeB_selector == 1:
            B_type= ConcreteB1
        elif typeB_selector == 2:
            B_type= ConcreteB2

        return B_type()


class ConcreteB1(AbstractB[ConcreteA1]):
    pass


class ConcreteB2(AbstractB[ConcreteA2]):
    pass

我试图了解

AbstractB.factory()
B_type
的返回值使用什么类型提示。根据我(诚然有限)对泛型的理解,我认为合适的类型应该是
AbstractB[AbstractA]
。然而,对于
strict=true
,mypy 给出了两条
B_type=...
行的错误;例如对于第一个:

Incompatible types in assignment (expression has type "type[ConcreteB1]", variable has type "type[AbstractB[AbstractA]]")
.

避免错误的唯一方法是使用

AbstractB[Any]
,如示例所示。然而,这对我来说感觉不对,因为我们知道类型参数绑定到
AbstractA
。我也尝试过
AbstractB[ATypeT]
,但这似乎也是错误的,并且还会导致 mypy 错误。

问题

  • 我认为正确的类型提示应该是正确的,还是我误解了事情?
    我在这里做了什么奇怪的事吗?根据我对工厂模式的理解,这似乎是一种有效的方法。
  • 注释

我的实际代码可以正确处理在子类之一上调用
    AbstractB[AbstractA]
  • 时的情况(
    factory()
    ConcreteB1
    )。
    关于SO有几个类似的问题(例如
  • 1
  • 2),但我相信我的情况不同,因为(当ConcreteB2
    factory()
    上调用时)没有办法告诉类型检查器哪个子类将被退回。
    
        
python-3.x generics abstract-class mypy python-typing
1个回答
0
投票

抽象类不应引用其依赖项

为了回答你的问题,是的,你在这里所做的一些事情很奇怪,但最奇怪的部分是

AbstractB

上的方法引用了它的依赖者

AbstractB
ConcreteB1
。抽象类应该代表子类的常见行为 - 通过尝试引用两个具体类,它实际上不再是常见行为,并且以某种方式需要了解它将用于的事情。希望这是有道理的。
解决方案

ConcreteB2

现在工厂只引用继承的类。您可能会想,等一下,我希望它返回抽象版本。实际上,通过将 if 语句映射到类,并集实际上可以更好地描述可能的结果。事实上,你可以更进一步,这样做:

from __future__ import annotations from abc import ABC from typing import Generic, TypeVar class AbstractA(ABC): pass class ConcreteA1(AbstractA): pass class ConcreteA2(AbstractA): pass ATypeT = TypeVar("ATypeT", bound=AbstractA) class AbstractB(ABC, Generic[ATypeT]): pass class ConcreteB1(AbstractB[ConcreteA1]): pass class ConcreteB2(AbstractB[ConcreteA2]): pass def factory(typeB_selector: int) -> ConcreteB1 | ConcreteB2: B_type: type[ConcreteB1] | type[ConcreteB2] if typeB_selector == 1: B_type= ConcreteB1 elif typeB_selector == 2: B_type= ConcreteB2 else: assert False return B_type()

现在很清楚什么映射到什么,并且我们还根据输入的数字获得具体类型。
关于抽象工厂的注释

您可以创建作为工厂工作的抽象类方法,但它们不能使用从它们继承的类,例如:

int

以上就可以了,因为它只引用了

@overload def factory(typeB_selector: Literal[1]) -> ConcreteB1: ... @overload def factory(typeB_selector: Literal[2]) -> ConcreteB2: ... def factory(typeB_selector: Literal[1,2]) -> ConcreteB1 | ConcreteB2: match typeB_selector: case 1: return ConcreteB1() case 2: return ConcreteB2()

© www.soinside.com 2019 - 2024. All rights reserved.