【mypy】データクラスである抽象基底クラスは、mypyに`Only concrete class can be given ~`というエラーで怒られる


環境

  • Python 3.9.7
  • mypy 0.920

やりたいこと

データクラスである抽象基底クラスAnimalを作りたいです。

foo.py
import abc
from dataclasses import dataclass


@dataclass(frozen=True)
class Animal(abc.ABC):
    name: str

    @abc.abstractmethod
    def cry(self):
        pass


@dataclass(frozen=True)
class Dog(Animal):
    color: str

    def cry(self):
        print("ワンワン")


@dataclass(frozen=True)
class Bird(Animal):
    can_fly: bool

    def cry(self):
        print("ガーガー")


def main():
    dog = Dog(name="alice", color="blue")
    dog.cry()

    bird = Bird(name="bob", can_fly=False)
    bird.cry()


if __name__ == "__main__":
    main()

$ python foo.py
ワンワン
ガーガー

mypyのエラーメッセージ

mypyで型チェックしたところ、以下のエラーメッセージが表示されました。

$ mypy foo.py
foo.py:5: error: Only concrete class can be given where "Type[Animal]" is expected
Found 1 error in 1 file (checked 1 source file)

原因

Stack OverFlowの質問によると、mypyのバグのようです。

I gather from mypy issue #5374 that this is a bug in mypy, first noticed in 2018 and still not corrected.

暫定的な解決方法

AnimalDataclassMixinクラスをデータクラスをmix-inクラスとして定義して、Animalクラスはそれを継承するようにしました。
上記のStack Overflowの回答通りの方法です。

@dataclass(frozen=True)
class AnimalDataclassMixin:
    name: str


class Animal(abc.ABC, AnimalDataclassMixin):
    @abc.abstractmethod
    def cry(self):
        pass

補足

mix-inクラスなのにメソッドが明示的に定義されていないのは、少し混乱するかもしれませんね。
以下、mix-inクラスの定義。

MixinまたはMix-in(ミックスイン)は[1][2][3][4]、オブジェクト指向プログラミングで用いられる技法であり、他のクラスから使用されるメソッド群を持つクラスが、他のクラスのスーパークラスにならないで済むための、特別な多重継承関係を実現するためのメカニズムを意味している。Mix-inされたメソッドに、他のクラスがアクセスする方法はそれぞれの言語仕様に依存している。

#type: ignoreで型チェックを無視するよりは良い方法ですが。