I'd like to type-annotate abstract class method witch behave as a constructor. For example in the code below, ElementBase.from_data
is meant to be a abstract classmethod constructor.
tmp.py
from abc import abstractmethod, abstractclassmethod
import copy
from typing import TypeVar, Type
ElementT = TypeVar('ElementT', bound='ElementBase')
class ElementBase:
data: int
def __init__(self, data): self.data
#@abstractmethod
def get_plus_one(self: ElementT) -> ElementT:
out = copy.deepcopy(self)
out.data = self.data 1
return out
@abstractclassmethod
def from_data(cls: Type[ElementT], data: int) -> ElementT: # mypy error!!!
pass
class Concrete(ElementBase):
@classmethod
def from_data(cls, data: int) -> 'Concrete': # mypy error!!!
return cls(data)
However, applying mypy to this code shows the following erros.
tmp.py:18: error: The erased type of self "Type[tmp.ElementBase]" is not a supertype of its class "tmp.ElementBase"
tmp.py:23: error: Return type "Concrete" of "from_data" incompatible with return type <nothing> in supertype "ElementBase"
Do you have any idea to fix this error? Also, I'm specifically confused that the part of get_plus_one
does not cause error, while only the part of abstractclassmethod
does cause the error.
FYI, I want to make the abstract method constructor generic becaues I want to statically ensure that all subclass of ElementBase
returns object with it's type when calling from_data
.
[EDIT] comment out abstractmethod
CodePudding user response:
It looks like mypy
doesn't understand the abstractclassmethod
decorator. That decorator has been deprecated since Python 3.3, as the abstractmethod
and classmethod
decorators were updated to play nice together. I think your code will work properly if you do:
@classmethod
@abstractmethod
def from_data(cls: Type[ElementT], data: int) -> ElementT:
pass
It's unrelated to your type checking issues, but you probably also want to change ElementBase
to inherit from abc.ABC
or to explicitly request the abc.ABCMeta
metaclass if you want the abstractness of the class to be enforced by Python. Regular classes don't care about the abstractmethod
decorator, and so as written, you'll be able to instantiate ElementBase
(or you could if it's __init__
method didn't have an unrelated issue).
And another peripherally related note on this kind of type hinting... PEP 673 will add typing.Self
in Python 3.11, which will be a convenient way for a method to refer to the type of object it's being called on. It should play nicely with classmethods without requiring you to jump through any hoops. With it you'd be able to write this much simpler version of the annotations:
@classmethod
@abstractmethod
def from_data(cls, data: int) -> Self:
pass