Pythonのdataclasses.dataclassは普段からたまに使っていますが、良く使っている書き方を人に紹介するためにメモを残します。
dataclasses - データクラス - Python 3.12.2 ドキュメント
dictと相互変換するクラス
オブジェクトとdictで相互変換するクラスをdataclassで書くことがあります。
asdict 関数が便利です。リストで保持したいメンバー変数は、 field 関数を使って定義すれば、asdictでそのまま対象にできます。
また from_dict メソッドは厳密に実装するなら引数のチェックなどをしてもよいですが、可変長のキーワード引数としてそのままコンストラクタに渡すように書けば、実装はシンプルです。
コード
main.py:
from dacite import from_dict from dataclasses import dataclass, asdict, field @dataclass class Person: name: str age: int @classmethod def from_dict(cls, data: dict): return cls(**data) @dataclass class Group: name: str members: list[Person] = field(default_factory=list) @classmethod def from_dict(cls, data: dict): members_data = data.get("members", []) instance = cls(**data) if members_data: instance.members = [ Person.from_dict(member_data) for member_data in members_data ] return instance def main(): person = Person(name="Alice", age=30) group = Group(name="Team1", members=[person]) # groupをdictに変換すると、membersの要素もdictに変換される data = asdict(group) print(data) # dictからGroupオブジェクトを作成すると、membersの要素もPersonオブジェクトに変換される group2 = Group.from_dict(data) print(group2) # daciteを使えば、from_dictメソッドがなくても、dataclassのネストしたオブジェクトを作成できる group3 = from_dict(data_class=Group, data=data) print(group3) if __name__ == "__main__": main()
実行結果
daciteを事前にインストール済み。
$ python main.py
{'name': 'Team1', 'members': [{'name': 'Alice', 'age': 30}]}
Group(name='Team1', members=[Person(name='Alice', age=30)])
Group(name='Team1', members=[Person(name='Alice', age=30)])
PersonとGroupのdataclassはどちらも親クラスの記述は無し。 from_dict クラスは明示的に実装しています。
fieldを読み取って自動的に from_dict の振る舞いをかえるような実装もできますが、継承関係を作らないのと、仕組みを複雑にしないためにあえてしていないです。
from_dict メソッドの実装を省略したい場合は、 dacite のようなサードパーティパッケージを使うのもよさそうです。