nikkie-ftnextの日記

イベントレポートや読書メモを発信

読書ログ | 『ロバストPython』14章、Pydanticのdataclassデコレータに切り替えるだけで実行時バリデーション追加!バリデータを書いて不変式を表現!なんて便利なの!

はじめに

PrayForKyoani、nikkieです。

久しぶりの『ロバストPython』読書ログ。
将来の開発者に意図を伝えるPythonの書き方が指南された(議論のための)本です。
Pydanticの関係1で14章を読みました。

目次

前回の『ロバストPython』!

発売当初、いくつか読書ログを書きました。

議論のための本なだけあって、各章で語りたいことが見つかり、読書会形式でPython使いと色々語れたらいいな〜と毎回感じます(どなたか🙏)

14章 pydanticによる実行時型チェック

監訳のHayaoさんによる書籍紹介スライド

読んだ後の感想ツイート

実行時型チェック

ロバストPython』では、将来の開発者に意図を伝えるために、の重要さが説かれます。
型ヒントを書いて型チェッカを実行するという話が分量としては多いのですが、14章で扱うのは実行時の話
例えばユーザ入力は不正なデータとなる可能性があります。
不正なデータで処理が進むよりは読み込んだ時点で例外を送出して落ちるのが望ましいですよね。

14章のサンプルコードはレストラン。
レストランで働く従業員や、提供する料理のメニューも扱っている、battery includedなデータです。
以下のようなYAMLファイルで表現されます。
https://github.com/pviafore/RobustPython/blob/dafb95d801dff2c8ff7856ba46d3c052d54e0033/code_examples/chapter14/restaurant.yaml

TypedDictの場合

TypedDictを使ってRestaurantが定義されます。

class Restaurant(TypedDict):
    ...

YAMLファイルを読み込むメソッドを以下のようにアノテーションします。
https://github.com/pviafore/RobustPython/blob/dafb95d801dff2c8ff7856ba46d3c052d54e0033/code_examples/chapter14/read_restaurant.py

def load_restaurant(filename: str) -> Restaurant:
    with open(filename) as yaml_file:
        return yaml.safe_load(yaml_file)

Restaurantとして不正なデータのYAMLは以下

これらの不正なRestaurantデータ、YAMLとしては壊れておらず読めちゃうので、実行時はload_restaurantRestaurant型の値(すなわち辞書)を返します。
ところが後続の処理中に不備が顕在化し、そこで落ちます。

Pydanticが提供する実行時型チェックによる解決

上記の事象はPydanticによって解決されます。
サンプルコード:https://github.com/pviafore/RobustPython/blob/dafb95d801dff2c8ff7856ba46d3c052d54e0033/code_examples/chapter14/restaurant_pydantic.py

@pydantic.dataclasses.dataclass2を使ってデータクラスをデコレート(9章の標準ライブラリのdataclassデコレータから、Pydanticのデコレータへ変えるだけ)。
たったこれだけで実行時型チェックが付加されます!

from pydantic.dataclasses import dataclass

@dataclass
class Restaurant:
    ...

読み込むメソッドはRestaurantクラスのインスタンスを初期化して返すように実装されます

def load_restaurant(filename: str) -> Restaurant:
    with open(filename) as yaml_file:
        data = yaml.safe_load(yaml_file)
        return Restaurant(**data)

不正なデータを渡すと、TypedDictの時と異なり、Pydanticによる検証でValidationErrorが送出されます。
不正なデータを使って処理が進むことはなく、load_restaurantで処理が落ちます3

指南された実行時型チェック

検証について教わりました。

頻出の制約はconstrやconlistで実装でき、カスタマイズした制約はデコレータを使って自由に実装できるという理解です。
写経コードはこちら:

終わりに

ロバストPython』14章「pydanticによる実行時型チェック」の読書ログでした。

  • 型チェックの隙間を埋める実行時型チェック
  • @dataclasses.dataclass@pydantic.dataclasses.dataclassに変えるだけで導入可能
    • 標準ライブラリのデータクラスと同様の書き方なのに、実行時のチェックが提供される!便利すぎ
  • Pydanticの各種バリデータを使ってバリデーションロジックを作り込める
    • 10章で強調された不変式!

データクラスに不変式が絡んだり(クラスとの間隙を埋めるという言い回し)、Pydanticはパースライブラリ(出力を保証する)だったり、興味深いポイント目白押しでした。
こんなに便利なPydantic、使う機会を増やして経験値を積んでいきたいですね。


  1. Pydanticのメジャーバージョンアップの素振りとして『ロバストPython』14章のコードがいいのではと考えました。
  2. 書籍に合わせてV1のドキュメントへのリンクです(この記事でPydanticのドキュメントへのリンクはV1で統一しています)。 https://docs.pydantic.dev/1.10/usage/dataclasses/
  3. 『達人プログラマー』にもTip 38 早めにクラッシュさせることとありますね(第2版を確認)