I'm trying to figure out the type hinting for a couple of abstract classes that I want to use as base classes for classes have a create function. Specifically, this is for deserialization typing.
My simple example looks like this
from abc import ABC, abstractmethod
from typing import Type, TypeVar
T = TypeVar("T", bound="A")
class A(ABC):
@classmethod
@abstractmethod
def create(cls: Type[T]) -> T:
pass
class B(A, ABC):
@classmethod
@abstractmethod
def create_b(cls: Type[T]) -> T:
pass
@classmethod
def create(cls) -> T:
return cls.create_b()
When I run Mypy against this I get
error: Incompatible return value type (got "B", expected "T")
I'm confused by this because B inherits from A, and I thought that T more or less represented "any A".
I can change the penultimate line to
def create(cls: Type[T]) -> T:
but then I get
error: "Type[T]" has no attribute "create_b"
What should I be doing to get mypy to pass?
Since
createis a class method, the argumentclshas typeType[B]. This means that theTas specified for argument and return types increate_bwill be resolved toBand therefore the expressioncls.create_b()has typeB. This results in the error you get.The confusing part here is probably that since
Bis a subtype ofAandTis bound toA, one might expect that it should be possible to returnBinB.create(). The problem is however, thatTwill be resolved whenB.create()is being used in some context and not earlier. In your example, you implicitly already enforceTto beB, which can lead to type errors.As an example, let's say we create the following class and function.
We can now use
B.create()as an argument offoo:The type checker won't complain here, because it resolves
T(the generic return type ofcreateasC, which is perfectly fine, sinceTis bound byAandCis a subtype ofA.Revisiting the function definition of
B.create(), we can now see that we actually must return the abstract typeTand not some permissible realization ofT, such asB(or evenA).TL;DR
You can fix your error by changing the return type of
B.create():