繁体   English   中英

Python 基础工厂方法的 3 类型提示 class 返回一个子实例 class 实例

[英]Python 3 type hint for a factory method on a base class returning a child class instance

假设我有两个类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以便 static 类型检查器可以推断Base.create()返回了Base的实例,而Child.create()返回了Child的实例,以便下面的示例将通过 static 分析?

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

在上面的示例中,static 类型检查器会合理地抱怨method_not_present_on_base不存在于Base class 中。


我想过将Base变成一个通用的 class 并让子类将它们自己指定为类型 arguments,即将CRTP变为 Python。

T = TypeVar('T')

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

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

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

这确实是可能的:这个特性被称为TypeVar with Generic Self (虽然这有点误导,因为在这种情况下我们将它用于类方法)。 我相信它的行为大致等同于您链接到的“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注释。

Python 3.11 现在有一个 Self 类型,以防其他人也偶然发现这个老问题。 mypy 正在努力支持它。

https://docs.python.org/3/library/typing.html#typing.Self

这是注释 class 方法工厂的返回值的一种 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。 我从 James Murphy 的视频中复制了下面的 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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM