返回子类实例的基类工厂方法的 Python 3 类型提示 [英] Python 3 type hint for a factory method on a base class returning a child class instance
问题描述
假设我有两个类 Base
和 Child
,在 Base
中有一个工厂方法.工厂方法调用另一个可能被 Base
的子类覆盖的类方法.
Let's say I have two classes Base
and Child
with a factory method in Base
. The factory method calls another classmethod which may be overriden by Base
's child classes.
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
的实例code> 和 Child.create()
返回了一个 Child
的实例,这样下面的例子就可以通过静态分析了吗?
Is there a way to annotate Base.create
so that a static type checker could infer that Base.create()
returned an instance of Base
and Child.create()
returned an instance of Child
, so that the following example would pass static analysis?
base = Base.create(1)
child = Child.create(2, 3)
child.method_not_present_on_base()
在上面的例子中,静态类型检查器会理所当然地抱怨 method_not_present_on_base
没有出现在 Base
类中.
In the above example a static type checker would rightfully complain that the method_not_present_on_base
is, well, not present on the Base
class.
我考虑将 Base
变成一个泛型类,并让子类将自己指定为类型参数,即引入 CRTP 到 Python.
I thought about turning Base
into a generic class and having the child classes specify themselves as type arguments, i.e. bringing the CRTP to Python.
T = TypeVar('T')
class Base(Generic[T]):
@classmethod
def create(cls, *args: Tuple) -> T: ...
class Child(Base['Child']): ...
但是对于来自 C++ 的 CRTP 和所有的东西,这感觉相当不合逻辑...
But this feels rather unpythonic with CRTP coming from C++ and all...
推荐答案
确实有可能:该功能名为 TypeVar with Generic Self(虽然这有点误导,因为在这种情况下我们将它用于类方法).我相信它的行为与您链接的CRTP"技术大致相同(尽管我不是 C++ 专家,因此不能肯定).
It is indeed possible: the feature is called TypeVar with Generic Self (though this is slightly misleading because we're using this for a class method in this case). I believe it behaves roughly equivalently to the "CRTP" technique you linked to (though I'm not a C++ expert so can't say for certain).
在任何情况下,您都可以像这样声明您的基类和子类:
In any case, you would declare your base and child classes like so:
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': ...
注意:
- 我们不需要使类本身通用,因为我们只需要一个通用函数
- 将 TypeVar 的绑定设置为 'Base' 严格来说是可选的,但可能是一个好主意:这样,您的基类/子类的调用者至少能够调用基类中定义的方法,即使您不知道您正在处理哪个子类.
- 我们可以省略子定义的
cls
注释.
这篇关于返回子类实例的基类工厂方法的 Python 3 类型提示的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!