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
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()
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.
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']): ...
But this feels rather unpythonic with CRTP coming from C++ and all...
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:
Note that:
cls
for the child definition.