あらまし
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点:
- 抽象クラスのコンストラクタの記述がちょっとめんどい。また、抽象クラス変数名を複数箇所に記述する必要がある
- サブクラスでコンストラクタをオーバーライドした場合、
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
方法②では抽象クラスをインスタンス化しようとしたタイミングでエラーが出たが、
こちらはクラス定義の時点でエラーとなる。
中間抽象クラスが絶対に不要ならばこれでもいいと思う。