Reputation: 678
What is the correct way to reuse the type of a class member to type hint other items in the class? As an example:
from typing import Type
class Model:
pass
class ChildModel:
childvar = "Child Model"
class Base:
var: Type[Model]
def fn(self) -> ??:
return self.var
class Child(Base):
var = ChildModel
def new_fn(self):
x = self.fn() # Type of x should be "ChildModel"
print(x.childvar)
Child().new_fn() # Prints "Child Model" successfully
I am looking for what would work to replace ??
such that the return type of fn()
can be inferred for all child classes.
MyPy does not accept changing ??
to Type[Model]
to match Base.var
: Incompatible types in assignment (expression has type "Type[ChildModel]", base class "Base" defined the type as "Type[Model]"
(though it is possible I made a mistake here). Even if this were allowed, this would allow Base.fn()
to return any Model
or Model
subclass, not strictly the type of var
(as defined in a child of Base
)
Something like T = TypeVar("T", bound=Type[Model])
seems disallowed without generics, which don't seem quite applicable since the type can be inferred without generic-style specification. I think the solution would likely also work to type hint method arguments, method-local variables, and other class member variables.
What is the best way to do this (if possible)?
Edit: adding clarification, corrected issue with code
Upvotes: 5
Views: 3151
Reputation: 104
Though this probably fails the "Explicit is better than Implicit" test, I suppose this will get you what you want while avoiding typing in two places. In this case, rather than defining var
on the Child
, the var
is pulled from the annotation.
Tested on Python 3.10
import typing
from typing import Generic, TypeVar
T = TypeVar("T", bound="Model")
class Model:
pass
class ChildModel(Model):
childvar = "Child Model"
class Base(Generic[T]):
@classmethod
@property
def var(cls) -> type[T]:
for superclass in cls.__orig_bases__:
if getattr(superclass, "__origin__", None) == Base:
return typing.get_args(superclass)[0]
def fn(self) -> type[T]:
return self.var
class Child(Base[ChildModel]):
def new_fn(self):
x = self.fn() # Type of x is type["ChildModel"]
print(x.childvar)
Upvotes: 1
Reputation: 3350
This can be accomplished with Generics.
from typing import Generic, TypeVar
T = TypeVar("T", bound="Model")
class Model:
pass
class ChildModel(Model):
childvar = "Child Model"
class Base(Generic[T]):
var: type[T]
def fn(self) -> type[T]:
return self.var
class Child(Base[ChildModel]):
var = ChildModel
def new_fn(self):
x = self.fn() # Type of x is type["ChildModel"]
print(x.childvar)
Child().new_fn()
Upvotes: 3