はじめに
ゴールデンウィークをだらだら過ごしていたらふと気になりました1。
実際どういうことかを「各チェックを行うクラス」を例に見ていきましょう。
概要
各チェックを行う元になる抽象基底クラスCheckBase
を下記のように定義します。
from abc import ABC, abstractmethod
class CheckBase(ABC):
"""各チェックIDのベースとなる抽象基底クラス"""
def __init__(self, data):
self.data = data
@abstractmethod
def is_target(self) -> bool:
"""チェック対象であればTrueを返す"""
...
@abstractmethod
def check(self) -> None:
"""チェックを行う"""
...
このCheckBase
を継承した各チェックを定義します。
class CheckID1(CheckBase):
def is_target(self) -> bool:
return True
def check(self) -> None:
print("チェックID1が実行された")
class CheckID2(CheckBase):
def is_target(self) -> bool:
return True
def check(self) -> None:
print("チェックID2が実行された")
このCheckBase
を継承したCheckID1
とCheckID2
のインスタンスを作成して処理をぶん回したいとします。
この実行を行うmain.py
は下記のように書くことにします。
import check
# 呼び出し部分だけ書く
for cls_ in [check.CheckID1, check.CheckID2]:
check_cls = cls_(check_data)
if check_cls.is_target():
check_cls.check()
このように書いてしまうと下記のような問題点に遭遇します。
-
CheckID
が増えていくと、[check.CheckID1, check.CheckID2]
も拡大していく -
CheckID
を実装したら、呼び出し側のmain.py
の改修が必要になる
この2つを解決するためには、CheckBase
を継承した派生クラスが動的に取得出来れば良さそうです。
派生クラスを動的に取得する
組み込みモジュールである inspect モジュールを使います。
下記に CheckBase
を継承した派生クラスだけを取得するメソッドを示します。
ざっくりやっていることは下記の2点です(難しいことはやっていないので詳しい説明は省略)。
-
get_all_check_cls
が定義されているモジュール内の全てのクラスを取得する
→ 今回の場合はcheck.py
に定義することにする -
CheckBase
の派生クラスでありCheckBase
自身ではないクラスを収集する
import sys
import inspect
def get_all_check_cls() -> list[type[CheckBase]]:
"""CheckBaseを継承した全てのクラスを取得する"""
ret = []
module = sys.modules[__name__]
# クラス名(CheckBase, CheckID1など)とそのオブジェクトがイテレートされる(今回クラス名は使わないので`_`にしている)
for _, obj in inspect.getmembers(module, inspect.isclass):
if issubclass(obj, CheckBase) and obj is not CheckBase:
ret.append(obj)
return ret
呼び出し側のmain.py
は、このget_all_check_cls
を呼び出すだけで済むので、新しいチェックが増えてもmain.py
に変更が生じないので嬉しくなれます。
-
だらだらしてたらゴールデンウィークが終わってしまって、こんな中途半端な時期に投稿することになってしまった… ↩