Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you annotate the type of an abstract class with mypy?

I'm writing a library where I need a method that takes a (potentially) abstract type, and returns an instance of a concrete subtype of that type:

# script.py
from typing import Type
from abc import ABC, abstractmethod


class AbstractClass(ABC):
    @abstractmethod
    def abstract_method(self):
        pass

T = TypeVar('T', bound=AbstractClass)

def f(c: Type[T]) -> T:
    # find concrete implementation of c based on
    # environment configuration
    ...


f(AbstractClass)  # doesn't type check

Running mypy script.py yields:

error: Only concrete class can be given where "Type[AbstractClass]" is expected

I don't understand this error message and am having a hard time finding any documentation for it. Is there any way to annotate the function so that mypy will type check this?

As a side note, PyCharm's type checker, which is what I use the most, type checks f with no errors.

like image 706
Sune Andreas Dybro Debel Avatar asked Jan 19 '18 20:01

Sune Andreas Dybro Debel


People also ask

What is abstract type class?

An abstract class is a class that is designed to be specifically used as a base class. An abstract class contains at least one pure virtual function. You declare a pure virtual function by using a pure specifier ( = 0 ) in the declaration of a virtual member function in the class declaration.

What are abstract classes JavaScript?

What are Abstract Classes? Abstract classes can be defined as classes that cannot be instantiated i.e. whose object reference cannot be created and contains within it, one or more abstract methods. An abstract method is a method that can only be declared but has no implementation to it.

How do I ignore MYPY errors?

Silencing errors based on error codes You can use a special comment # type: ignore[code, ...] to only ignore errors with a specific error code (or codes) on a particular line. This can be used even if you have not configured mypy to show error codes.

Can we use abstract in JavaScript?

To create an abstract class in JavaScript, we will need to modify the constructor a bit to handle the same class instantiation. To create an abstract class we need to check if constructor has same name as class and if true then throw an error .


1 Answers

It does appear that mypy is a bit biased against using an abstract base class this way, though as you demonstrate there are valid use cases.

You can work around this by making your factory function a class method on your abstract class. If stylistically you'd like to have a top-level function as a factory, then you can create an alias to the class method.

from typing import TYPE_CHECKING
from abc import ABC, abstractmethod


class AbstractClass(ABC):
    @abstractmethod
    def abstract_method(self):
        raise NotImplementedError

    @classmethod
    def make_concrete(cls) -> 'AbstractClass':
        """
        find concrete implementation based on environment configuration
        """
        return A()


class A(AbstractClass):
    def abstract_method(self):
        print("a")

# make alias
f = AbstractClass.make_concrete
x = f()
if TYPE_CHECKING:
    reveal_type(x)  # AbstractClass

Note that, without more work, mypy cannot know which concrete class is created by the factory function, it will only know that it is compatible with AbstractClass, as demonstrated by the output of reveal_type.

Alternately, if you're willing to give up the runtime checking provided by abc.ABC, you can get something even closer to your original design:

from typing import TYPE_CHECKING
from abc import abstractmethod


class AbstractClass:  # do NOT inherit from abc.ABC
    @abstractmethod
    def abstract_method(self):
        raise NotImplementedError


class A(AbstractClass):
    def abstract_method(self):
        print("a")


class Bad(AbstractClass):
    pass


def f() -> AbstractClass:
    """
    find concrete implementation based on environment configuration
    """
    pass

b = Bad()  # mypy displays an error here:  Cannot instantiate abstract class 'Bad' with abstract attribute 'abstract_method'

x = f()
if TYPE_CHECKING:
    reveal_type(x)  # AbstractClass

This works because mypy checks methods marked with @abstractmethod even if the class does not inherit from abc.ABC. But be warned that if you execute the program using python, you will no longer get an error about instantiating the Bad class without implementing its abstract methods.

like image 75
chadrik Avatar answered Oct 04 '22 01:10

chadrik