I want to make sure that the from_dict
in the following method works well in its subclasses as well. Currently, its typing does not work (mypy error "Incompatible return value type"). I think because the subclass is returning an instance of the subclass and not an instance of the super class.
from __future__ import annotations
from abc import ABC
from dataclasses import dataclass
from typing import ClassVar, Type
@dataclass
class Statement(ABC):
@classmethod
def from_dict(cls) -> Statement:
return cls()
@dataclass
class Parent(ABC):
SIGNATURE_CLS: ClassVar[Type[Statement]]
def say(self) -> Statement:
# Initialize a Statement through a from_dict classmethod
return self.SIGNATURE_CLS.from_dict()
@dataclass
class ChildStatement(Statement):
pass
@dataclass
class Child(Parent, ABC):
SIGNATURE_CLS = ChildStatement
def say(self) -> ChildStatement:
# Initialize a ChildStatement through a from_dict classmethod
# that ChildStatement inherits from Statement
return self.SIGNATURE_CLS.from_dict()
The code above yields this MyPy error:
Incompatible return value type (got "Statement", expected "ChildStatement") [return-value]
I think this is a use case for TypeVar
in Statement
but I am not sure how to implement and - especially - what the meaning behind it is.
Subclass instance is an instance of its super class, according to typing rules. The error you are seeing is because from_dict
is typed to be returning a Statement
, and you are trying to return that value from say
, which is guaranteed to return ChildStatement
. So your problem is that you could potentially return more generic Statement
where more specific ChildStatement
is expected.
You need to somehow ensure MyPy that:
from_dict
returns its actual class, not generic Statement
.SIGNATURE_CLS
of Child
will be ChildStatement
and not possibly Statement
(just assigning ChildStatement
is not enough as you explicitly typed it as ClassVar[Type[Statement]]
You can do that with following pieces of code:
return cls()
from typing import Self
@dataclass
class Statement(ABC):
@classmethod
def from_dict(cls) -> Self:
return cls()
(It's important to note that typing.Self
is a very fresh addition introduced in Python 3.11, you might also need to update your MyPy to be able to take this into account)
Child.SIGNATURE_CLS
@dataclass
class Child(Parent, ABC):
SIGNATURE_CLS : ClassVar[Type[ChildStatement]] = ChildStatement
As a final note, I wonder about the sensibility of Statement.from_dict()
method, as it isn't any different than just using the standard constructor of Statement()
.