0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Pythonにおける抽象クラス変数の定義方法についての考察

Posted at

あらまし

Pythonでプログラムを設計してて、「抽象クラス変数」を使いたいなーと思いました。

イメージ

from abc import ABC

class AbstractClass(ABC):
    name: ABSTRACT[str] # ←イメージ

    def greet(self):
        return f'Hello, I am {self.name}.'

class ConcreteClass(AbstractClass):
    name = 'Alice' # ←具象クラスで値を定義

class InvalidConcreteClass(AbstractClass):
    pass # ←定義しなかったら型チェックでエラーにしたい

ただ、abc --- 抽象基底クラス — Python 3.13.2 ドキュメントをいくら読んでもそれっぽい方法がない・・・
そのため、ネット検索して出てきた方法を書いてみる

方法①: クラスプロパティにする

from abc import ABC, abstractmethod

class AbstractClass(ABC):
    @classmethod
    @property
    @abstractmethod
    def name(cls) -> str:
        ...

    def greet(self):
        return f'Hello, I am {self.name}.'

class ConcreteClass(AbstractClass):
    @classmethod
    @property
    def name(cls) -> str:
        return 'Bob'

c = ConcreteClass()
c.greet()
# => 'Hello, I am Bob.'

Copilotに聞いたらこの方法を提案された。
ただ、見てのとおりかなりデコレータとプロパティ定義に長い記述が必要になる。

方法②: 抽象クラスのコンストラクタでチェックする

from abc import ABC

class AbstractClass(ABC):
    name: str

    def __init__(self):
        if not hasattr(self, 'name'):
            raise TypeError(f"Can't instantiate abstract class {self.__class__.__name__} with abstract class variable name")
        super().__init__()

    def greet(self):
        return f'Hello, I am {self.name}.'

class ConcreteClass(AbstractClass):
    name = 'Charlie'

class InvalidConcreteClass(AbstractClass):
    ...

c = ConcreteClass()
c.greet()
# => 'Hello, I am Charlie.'
InvalidConcreteClass()
# => TypeError: Can't instantiate abstract class InvalidConcreteClass with abstract class variable name

今回採用したのはコレ。
どこかのウェブサイトで見つけたが、URLを失念してしまった。。。見つけたら更新します。
考え方としては抽象クラスではクラス変数の宣言だけしておいて、
コンストラクタの中で実在確認をするように仕込んでおく。

デメリットとしては2点:

  1. 抽象クラスのコンストラクタの記述がちょっとめんどい。また、抽象クラス変数名を複数箇所に記述する必要がある
  2. サブクラスでコンストラクタをオーバーライドした場合、 super().__init__() をちゃんと呼んでくれないとチェックがスキップされてしまう。(強制できない)

方法③: クラス継承時にチェックする

from abc import ABC

class AbstractClass(ABC):
    name: str

    def __init_subclass__(cls, *args, **kwargs):
        if not hasattr(cls, 'name'):
            raise TypeError(f"Can't inherit abstract class {cls.__name__} with abstract class variable name")
        super().__init_subclass__(*args, **kwargs)

    def greet(self):
        return f'Hello, I am {self.name}.'

class ConcreteClass(AbstractClass):
    name = 'Charlie'

c = ConcreteClass()
c.greet()
# => 'Hello, I am Charlie.'

class InvalidConcreteClass(AbstractClass):
    ...
# => TypeError: Can't inherit abstract class InvalidConcreteClass with abstract class variable name

方法②では抽象クラスをインスタンス化しようとしたタイミングでエラーが出たが、
こちらはクラス定義の時点でエラーとなる。
中間抽象クラスが絶対に不要ならばこれでもいいと思う。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?