16

mypy v0.910 rejects abstract dataclasses in Python 3.9. Here's the Minimal Reproducible Example:

from abc import ABC, abstractmethod
from dataclasses import dataclass

@dataclass
class Liquid(ABC):

    @abstractmethod
    def drip(self) -> None:
        pass

Here's the error message:

$ mypy --python-version 3.9 so.py
so.py:4: error: Only concrete class can be given where "Type[Liquid]" is expected
Found 1 error in 1 file (checked 1 source file)

How do I get this code to pass mypy?


Notes

I gather from mypy issue #5374 that this is a bug in mypy, first noticed in 2018 and still not corrected. I figure that people must be using mypy with abstract dataclasses, though, so there must be a workaround or a correct way to define or annotate the class. What is recommended?

The basis for the error message seems to be that mypy assumes that any object of type Type can be instantiated, but abstract classes cannot be instantiated. This appears to be the error since Type is defined to mean a class object, not necessarily a concrete class object (i.e. one that can be instantiated).

Adding # type: ignore to the line containing class Liquid does not block the error message. Since the code does not contain Type[Liquid], I figure it must be in the code generated by dataclass. Type is deprecated in Python 3.9, but apparently the dataclass code generator still generates it.

Alex Waygood
  • 6,304
  • 3
  • 24
  • 46
Ben Kovitz
  • 4,920
  • 1
  • 22
  • 50
  • 1
    Out of curiosity, what is the purpose of using the dataclass decorator here? – juanpa.arrivillaga Sep 25 '21 at 21:46
  • 1
    @juanpa.arrivillaga The purpose is only to make a Minimal Reproducible Example. The dataclasses in my production code have member attributes. I just double-checked, and the example does fail if I add a member attribute. – Ben Kovitz Sep 25 '21 at 21:54

3 Answers3

11

Create a dataclass as a mixin and let the ABC inherit from it:

from abc import ABC, abstractmethod
from dataclasses import dataclass    

@dataclass
class LiquidDataclassMixin:
    my_var: str

class Liquid(ABC, LiquidDataclassMixin):
    @abstractmethod
    def drip(self) -> None:
        pass

This works with mypy type checking as well. I recommend against using # type: ignore as that defeats the point of type-checking. Taken from this GitHub issue.

Nicolas Forstner
  • 428
  • 3
  • 11
  • When I first saw this approach, I was worried that it would create clutter since each abstract dataclass needs a separate mix-in class. But I just went through my entire codebase and only found four abstract dataclasses, so this is definitely workable. Thanks! – Ben Kovitz Sep 28 '21 at 10:28
  • I just unaccepted this answer because of a problem explained in [this question](https://stackoverflow.com/q/70999513/1393162). I would much prefer to keep type-checking on, so if you (or anyone) can tell me how to avoid that problem, I'll re-accept this answer. I also posted a solution there, with an explanation. Please take a look and see what you think. – Ben Kovitz Feb 05 '22 at 16:10
  • Say, what is the reason for putting ABC ahead of LiquidDataclassMixin? This might be a crucial piece of the puzzle. – Ben Kovitz Feb 07 '22 at 00:10
3

Add # type: ignore to the decorator line. So in your case it would be:

from abc import ABC, abstractmethod
from dataclasses import dataclass

@dataclass  # type: ignore[misc]
class Liquid(ABC):

    @abstractmethod
    def drip(self) -> None:
        pass
  • 1
    After a lot more coding and running into more problems, I've decided that turning off type-checking in the decorator line is indeed the correct solution, as explained in [this answer](https://stackoverflow.com/a/70999704/1393162). If you change `type: ignore` to `type: ignore[misc]`, I'll accept this answer. (I'll edit it myself if I don't hear from you soon.) – Ben Kovitz Feb 05 '22 at 16:12
  • 1
    Corrected. Thanks for the update, and sorry for such a late response :) – Kamil Tagowski Feb 02 '23 at 11:58
1

As indicated in the merge request "Use a dedicated error code for abstract type object type error", we will soon be able to disable it globally either using --disable-error-code=type-abstract in the command line or adding disable_error_code =type-abstract to the configuration file. (Possibly from the coming v0.983)

For non-global use cases, it might be sufficient to add # type: ignore[type-abstract] at the end of the respective line.

Moore
  • 453
  • 1
  • 6
  • 10