返回子类示例的基类的工厂方法的Python 3类型提示

7d7tgy0s  于 2023-02-06  发布在  Python
关注(0)|答案(2)|浏览(122)

假设我有两个类BaseChild,在Base中有一个工厂方法,工厂方法调用另一个类方法,这个类方法可能被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()返回了Base的示例,Child.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']): ...

但这感觉相当unpythonic与CRTP来自C++和所有...

osh3o9ms

osh3o9ms1#

确实有可能:这个特性被称为TypeVar with Generic Self(尽管这有点误导,因为我们在本例中将其用于类方法)。我相信它的行为大致等同于您链接到的"CRTP"技术(尽管我不是C++Maven,所以不能肯定)。
在任何情况下,您都可以像这样声明基类和子类:

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.我们不需要使类本身泛型化,因为我们只需要一个泛型函数
1.严格来说,将TypeVar的绑定设置为'Base'是可选的,但这可能是一个好主意:这样,基类/子类的调用者至少能够调用基类中定义的方法,即使你不知道你正在处理的是哪个子类。
1.对于子定义,我们可以省略cls上的注解。

2ledvvac

2ledvvac2#

Python 3.11现在有一个Self类型,以防其他人也碰到这个老问题。mypy正在致力于支持它。
https://docs.python.org/3/library/typing.html#typing.Self
这是一种标注类方法工厂返回值的DRY方法:

from typing import Self
from collections import defaultdict

class NestedDefaultDict(defaultdict):
    def __init__(self, *args, **kwargs):
      super().__init__(NestedDefaultDict, *args, **kwargs)
      
    @classmethod  
    def from_nested_dict(cls, dict_) -> Self:
        inst = NestedDefaultDict()
        for key, val in dict_.items():
            inst[key] = cls.from_nested_dict(val) if isinstance(val, dict) else val
        return inst

Self对于方法链接API也很棒,我从JamesMurphy的video中复制了下面的Self-less示例,并添加了3个注解。

# https://github.com/mCodingLLC/VideosSampleCode/blob/master/videos/095_method_chaining_and_self/method_chaining_and_self.py

class Player:
    def __init__(self, name, position, fatigue=0):
        self.name = name
        self.position = position
        self.fatigue = fatigue

    def draw(self) -> Self:
        print(f"drawing {self.name} to screen at {self.position}")
        return self

    def move(self, delta) -> Self:
        self.position += delta
        self.fatigue += 1
        return self

    def rest(self) -> Self:
        self.fatigue = 0
        return self

相关问题