31

假设我有两个类Base,并且ChildBase. 工厂方法调用另一个可能被Base的子类覆盖的类方法。

class Base(object):
    @classmethod
    def create(cls, *args: Tuple) -> 'Base':
        value = cls._prepare(*args)
        return cls(value)

    @classmethod
    def _prepare(cls, *args: Tuple) -> Any:
        return args[0] if args else None

    def __init__(self, value: Any) -> None:
        self.value = value


class Child(Base):
    @classmethod
    def _prepare(cls, *args: Tuple) -> Any:
        return args[1] if len(args) > 1 else None

    def method_not_present_on_base(self) -> None:
        pass

有没有办法进行注释Base.create,以便静态类型检查器可以推断Base.create()返回的实例BaseChild.create()返回的实例Child,以便以下示例通过静态分析?

base = Base.create(1)
child = Child.create(2, 3)
child.method_not_present_on_base()

在上面的示例中,静态类型检查器会正确地抱怨类method_not_present_on_base中不存在Base


我想过Base变成一个泛型类并让子类将自己指定为类型参数,即将CRTP引入 Python。

T = TypeVar('T')

class Base(Generic[T]):
    @classmethod
    def create(cls, *args: Tuple) -> T: ...

class Child(Base['Child']): ...

但这对于来自 C++ 的 CRTP 和所有...

4

1 回答 1

28

这确实是可能的:该功能被称为带有 Generic Self 的 TypeVar(尽管这有点误导,因为在这种情况下我们将它用于类方法)。我相信它的行为与您链接到的“CRTP”技术大致相同(尽管我不是 C++ 专家,所以不能肯定地说)。

在任何情况下,你都会像这样声明你的基类和子类:

from typing import TypeVar, Type, Tuple

T = TypeVar('T', bound='Base')

class Base:
    @classmethod
    def create(cls: Type[T], *args: Tuple[Any]) -> T: ...

class Child(Base):
    @classmethod
    def create(cls, *args: Tuple[Any]) -> 'Child': ...

注意:

  1. 我们不需要使类本身泛型,因为我们只需要一个泛型函数
  2. 严格来说,将 TypeVar 的绑定设置为“Base”是可选的,但可能是一个好主意:这样,即使您没有,基类/子类的调用者至少能够调用基类中定义的方法确切地知道您正在处理哪个子类。
  3. 我们可以省略子定义的注释cls
于 2017-09-05T22:41:50.987 回答