dataclassのJSONパーサー
pythonでデータを扱う際に便利なdataclassを、
JSONでシリアライズすることにより、
構成ファイルを作成します。
dataclassの仕様
データの集合を定義するクラスです。dataclassデコレータで修飾することで利用できます。
クラス変数がデータメンバとして扱われます。クラス変数を初期化するためのコンストラクタが自動的に作成されます。
クラス変数にデフォルト値を設定することができ、コンストラクタではキーワード引数により初期化出来ます。dataclassデコレータで並び順の指定を行わなかった場合、クラス変数の定義順と、コンストラクタにおける仮引数の順番が同一になります。キーワード引数は位置引数よりも後に定義しなければならないという規則が適用されるので、クラス変数の定義順には注意が必要です。
asdict関数でdataclassを辞書に変換することが出来ます。dataclassのコンストラクタに辞書をアンパックして渡すことで初期化が可能なので、辞書との相互変換が可能です。
from dataclasses import dataclass @dataclass class ConfigData: config1: str config2: str = "value2" config3: int = 3
dataclassの利点
- 変数が入力候補に表示される
configparserなどで用いるデータはアクセスしたいデータのキーを文字列で入力しなければなりません。
dataclassを用いれば、IDEのインテリセンスで入力候補が表示されるので、タイプミスのリスクを低減出来ます。
- コンストラクタを省略できる
データしか定義しないクラスで、コンストラクタをいちいち定義するのは冗長です。
dataclassを用いることで、コンストラクタが自動生成されるのでコード量を減らすことが出来ます。
また、コンストラクタがatomic mutatorであることが保証されます。つまり、仮引数の定義忘れによる変数未初期化という障害を回避できます。
dataclassのJSONエンコーダー
class Dataclass2JSONEncoder(json.JSONEncoder): def default(self, o): if dataclasses.is_dataclass(o): return dataclasses.asdict(o) return super().default(o)
dataclassは辞書と相互変換できるので、JSONにおいても辞書データとして扱います。
JSONEncoderクラスを継承させたクラスを作成し、default関数のオーバーライド内で、オブジェクトがdataclassかどうか判定し、真だった場合は辞書に変換して返しています。
Dataclass構成ファイルシリアライザ
class DataclassConfigSerializer: def __init__(self, path='./config.json'): self._path = path def load_config(self, data_type): return data_type(** self.load_config_asdict()) def load_config_asdict(self): with open(self._path, mode='r', encoding='utf-8') as f: dict_data = json.load(f) return dict_data def save_config(self, data): with open(self._path, mode='w', encoding='utf-8') as f: json.dump(data, f, cls=Dataclass2JSONEncoder, indent=4)
- コンストラクタ
構成ファイルの配置pathを指定します。
- save_config関数
dataclassを渡しシリアライズします。
dump関数に前の項目で作成したエンコーダを指定しています。
- load_config関数
dataclass構成ファイルの型を指定します。
load_config_asdict関数で構成ファイルを辞書として読み込みます。
引数として渡された型に()をつけることでコンストラクタにアクセス出来ます。引数として辞書のアンパックを渡します。
利用例
def test_save_config(self): serializer = DataclassConfigSerializer() serializer.save_config(ConfigData('1', '2', 3)) def test_load_config(self): serializer = DataclassConfigSerializer() data: ConfigData = serializer.load_config(ConfigData)