Pythonのdataclasses.dataclassを使う

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 のようなサードパーティパッケージを使うのもよさそうです。