🐍

Attrs と Dataclasses と Pydantic の使いわけを調べてみた

2023/01/16に公開

FastAPIを用いた開発を最近始めましたが、PythonでDDDでいうValueObjectなどを定義するときに人によってDataclassesのdataclassであったりpydantic.dataclassesのdataclassやBaseModelを使っているのを目にしました。

ここで、自分の開発では何が一番適切なのかが不明確だったので調べることにしました。

具体的なそれぞれのライブラリの説明は割愛しますが各ドキュメントのURLは以下の通りです。

結論

前提として筆者はFastAPIを用いた開発をしていますが、基本的に以下のような形で決まるのではと思います。

  1. __slots__を使ったクラス定義をしたい(速度を意識したい) → Attrs
  2. __slots__を使う予定がないかつ標準ライブラリを使いたい → Dataclasses
  3. Dataclassesでバリデーションをしたい/Pydanticでdataclass使いたい → Pydantic(のdataclass)
  4. 特にこだわりがない → Pydantic(のBaseModel)

大枠の比較に関しては以下の引用がわかりやすいです。


Jack McKew's Blog: Dataclasses vs Attrs vs Pydantic: Features

Attrsの使いどころ

  • __slots__を使ったクラス定義をしたい(速度を意識したい)

attrsのつかいどころに関しては、Keep on moving : attrsの使いどころとdataclassにて記載されていました。

  • 今Python3.5 or 3.6を使っている.
    • 特に現在namedtupleを使っているが、3.7にupgradeするタイミングでdataclassの使用を考えている
  • dataclassで slotsなどをつかいたい(for Python3.7以降)

dataclassをつかったクラスでも__slots__を定義すれば設定できます
しかし何回も書くのが億劫な場合はattrsを使ったほうがよさそうです。

Dataclassesの使いどころ

  • __slots__を使う予定がないかつ標準ライブラリを使いたい

Python3.10ではDataclassでslotsが使えます

Dataclassesの使いどころに関しては、「データに関する堅牢性と可読性を向上させるpydanticとpanderaの活用方法の提案」の質疑応答: Q:dataclassとpydanticの違いは何ですか?にて記載されていました。

以下は表を引用した上で抜粋したものになります。

比較軸 Dataclasses Pydantic
インストールの要否 標準ライブラリである。PyPIからのインストールは不要。 外部ライブラリである。PyPIからのインストールが必要。
バリデーションの実現方法 クラスおよびフィールドの定義のみではバリデーションが実施されない。メソッドを定義することにより詳細なバリデーションの実現が可能である。 クラスおよびフィールドの定義のみで型についてのバリデーションが実施される。Fieldモジュールにより簡単なバリデーションの実現が可能である。メソッドを定義することにより詳細なバリデーションの実現が可能である。
シリアライズ・デシリアライズの仕様(辞書型) 辞書型へのシリアライズ・デシリアライズが可能。ただし、ネストされている要素に関してはインスタンスには変換されず、辞書型として格納される。 辞書型とのシリアライズ・デシリアライズが可能。辞書がネストされている場合も意図どおりインスタンスをの生成が可能である。
シリアライズ・デシリアライズの仕様 (JSON) dataclasses-jsonという外部ライブラリを使用することでJSONとのシリアライズ・デシリアライズが可能。ネストされている場合も意図どおりインスタンスをの生成が可能である。 JSONとのシリアライズ・デシリアライズが可能。JSONがネストされている場合も意図どおりインスタンスをの生成が可能である。

基本的にはPydanticのdataclassの方が優れているように(dataclassでは型ヒントを強制をしてくれないので事故が起きうるなど)感じましたが、標準ライブラリである点などで使うケースなどがあると思われます。

Pydanticの使いどころ

共通したPydanticの使いどころ

型の安全性を上げたい

Dataclassesでは型ヒントと異なるコードがランタイムエラーになりません。

しかしPydanticであればランタイムエラーをしてくれます。

ちなみにですが、公式としては pydanticは 入力データのvalidationのライブラリではなく、出力データの型の保証をするparseのライブラリである と言及しています。

pydantic is primarily a parsing library, not a validation library.
Validation is a means to an end: building a model which conforms to the types and constraints provided.
In other words, pydantic guarantees the types and constraints of the output model, not the input data.
pydantic Modelsより引用

Validationをしたい

Pydanticにはカスタムバリデーションが機能があり、主に以下のようなメリットがあります。

  1. @validatorデコレータを使うだけで各フィールドにバリデーションを簡単に実装できる
  2. バリデーションの再利用がしやすい(バリデーションを複数ヶ所で使う際にはvalidatorのallow_reuse引数にTrueを渡すだけでよい)

他にもさまざまな機能を提供しており柔軟性が高い印象です。

dataclassとBaseModelの使いわけ

  • dataclassesでバリデーションをしたい/Pydanticでdataclass使いたい → Pydantic(のdataclass)
  • 特にこだわりがない → Pydantic(のBaseModel)

dataclassを使いたいという強い想いがなければBaseModelでやや良いのではと個人的には考えていますが好みの話になそうです。
Pydantic: dataclass vs BaseModelでは、具体的にinit関数の動作の違いを説明しています。

こちらを踏まえると、機能的な観点では指定した型ヒントに沿ってcastしてくれるBaseModelの方がややそう?くらいの印象です。

公式PyadanicのDataclassesの説明で両者の違いについて述べているので興味がある方はみると良いと思われます。

参考

Discussion