Home > front end >  How to add type annotation to abstract classmethod constructor?
How to add type annotation to abstract classmethod constructor?

Time:02-22

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
  • Related