この記事ではPythonライブラリのpydanticの使い方について説明します。pydanticは組み込みモジュールのdataclassesの上位互換のようなライブラリで、以下の機能を有します。
- 型アノテーションを利用してクラスの属性(変数)とその型を定義する機能
- オブジェクトのコピーやシリアライズ/デシリアライズ等を行う機能
- 属性の型や制約条件に対するバリデーション機能
単体としても非常に便利ですが、fastapiなどpydanticを利用したモダンなライブラリが増えてきているため、覚えておいて損はないと思います。
公式のドキュメントが丁寧に書かれているため、この記事ではチートシート的なサンプルコードをまとめました。
クラスの定義とインスタンスの作成
基本的な使い方は、pydantic.BaseModel
を継承したクラスに、クラス属性を型アノテーションで定義するだけです。__init__
メソッドを個別定義したかのようにインスタンスを生成することができます。
from typing import Optional from pydantic import BaseModel ################################################# # (1) クラスの定義 ################################################# # `BaseModel`を継承したクラスで # 型アノテーション付きのクラス属性を定義する。 class User(BaseModel): id: int # ユーザID name: str # ユーザ名 height: Optional[float] # 身長[cm] weight: Optional[float] # 体重[kg] ################################################# # (2) インスタンスの作成 ################################################# # 通常のクラスと同様にインスタンスを作成できる。 user1 = User(id=1, name="田中", height=142.0) print(user1) #> id=1 name='田中' height=142 weight=None
オブジェクト作成時に、型が合っていない場合は例外が発生します。
user2 = User(id=2, name="田中", height="height") # ====== 実行結果 ======= Traceback (most recent call last): File "pydantic_sample1.py", line 21, in <module> user2 = User(id=2, name="田中", height="height") File "pydantic/main.py", line 331, in pydantic.main.BaseModel.__init__ pydantic.error_wrappers.ValidationError: 1 validation error for User height value is not a valid float (type=type_error.float)
インスタンス作成時は、キーワード引数を使用する必要があります。位置引数は使えません。言い換えると「id=」などは省略できません。
user3 = User(3, "佐藤", 138.0, 42.0) # ====== 実行結果 ======= Traceback (most recent call last): File "pydantic_sample1.py", line 79, in <module> user3 = User(2, "佐藤", 138.0, 42.0) File "pydantic/main.py", line 322, in pydantic.main.BaseModel.__init__ TypeError: __init__() takes exactly 1 positional argument (5 given)
Optional
以外の引数が足りないと例外が発生します。
user4 = User(id=4, height=139.0, weight=41.0) # ====== 実行結果 ======= Traceback (most recent call last): File "pydantic_sample1.py", line 89, in <module> user4 = User(id=4, height=139.0, weight=41.0) File "pydantic/main.py", line 331, in pydantic.main.BaseModel.__init__ pydantic.error_wrappers.ValidationError: 1 validation error for User name field required (type=value_error.missing)
便利な事前定義メソッド
BaseModel
にはいくつかの便利なメソッドが定義されており、サブクラスからも利用することができます。メソッドの一部を以下に示します。
メソッド | 説明 |
dict() | オブジェクトの属性と値を辞書形式で返す。 |
json() | dict() の辞書をJSON文字列形式で返す。 |
copy() | オブジェクトのコピーを返す。 |
parse_obj() | 属性と値の辞書からオブジェクトを作成する。 |
parse_raw() | 属性と値の辞書を表すJSON文字列からオブジェクトを作成する。 |
schema() | クラスのJSONスキーマを辞書形式で返す。 |
schema_json() | クラスのJSONスキーマをJSON文字列形式で返す。 |
いくつかのメソッドの実行例を以下に示します。
from typing import Optional from pydantic import BaseModel ################################################# # (1) クラスの定義 ################################################# # `BaseModel`を継承したクラスで # 型アノテーション付きのクラス属性を定義する。 class User(BaseModel): id: int # ユーザID name: str # ユーザ名 height: Optional[float] # 身長[cm] weight: Optional[float] # 体重[kg] ################################################# # (2) インスタンスの作成 ################################################# # 通常のクラスと同様にインスタンスを作成できる。 user1 = User(id=1, name="田中", height=142.0) print(user1) #> id=1 name='田中' height=142 weight=None ################################################# # (3) メソッドのサンプル ################################################# # JSON形式でインスタンスの属性を出力できる。 print(user1.json()) #> {"id": 1, "name": "田中", "height": 142, "weight": null} # JSONスキーマも出力できる。 print(User.schema_json(indent=2)) """{ "title": "User", "type": "object", "properties": { "id": { "title": "Id", "type": "integer" }, "name": { "title": "Name", "type": "string" }, "height": { "title": "Height", "type": "integer" }, "weight": { "title": "Weight", "type": "integer" } }, "required": [ "id", "name" ] } """ # インスタンスの複製ができる。 user1_2 = user1.copy(deep=True) print(user1_2) #> id=1 name='田中' height=142.0 weight=None # JSONからインスタンスを生成できる。 user1_3 = User.parse_raw('{"id": 1, "name": "田中", "height": 142.0}') print(user1_3) #> id=1 name='田中' height=142.0 weight=None
属性のバリデーションと制約
validator
デコレータで、クラスの各属性をバリデーションする機能や値の範囲などの制約を追加することができます。
from pydantic import BaseModel, validator # 人間クラス class Person(BaseModel): id: int # ユーザID name: str # ユーザ名 height: float # 身長[cm] @validator("height") # 属性名を指定 def height_must_be_smaller_than_250(cls, v): if v >= 250: raise ValueError("身長は250cm以下を入力") return v # 指定した型に変換可能な値であれば自動変換される。 person1 = Person(id="1", name="田中", height="144.3") print(person1) # `validator`で指定した制約を自動チェック。 person2 = Person(id="2", name="佐藤", height="300") # ====== 実行結果 ======= Traceback (most recent call last): File "pydantic_sample3.py", line 23, in <module> person2 = Person(id="2", name="田中", height="300") File "pydantic/main.py", line 331, in pydantic.main.BaseModel.__init__ pydantic.error_wrappers.ValidationError: 1 validation error for Person height 身長は250cm以下を入力 (type=value_error)
特殊な型を指定したり、Field
の引数で制約を追加することもできます。
from pydantic import BaseModel, Field, StrictInt # 人間クラス class Person(BaseModel): id: StrictInt # ユーザID(自動変換を認めない厳密なint) name: str # ユーザ名 height: float = Field(..., lt=300) # 身長[cm](範囲の制約を設定) # StrictIntに"2"は指定できない。 person1 = Person(id="2", name="鈴木", height="144.3") # ====== 実行結果 ======= Traceback (most recent call last): File "pydantic_sample4.py", line 43, in <module> person1 = Person(id="2", name="鈴木", height="144.3") File "pydantic/main.py", line 331, in pydantic.main.BaseModel.__init__ pydantic.error_wrappers.ValidationError: 1 validation error for Person id value is not a valid integer (type=type_error.integer) # Fieldに指定した制約も正しく機能する。 person2 = Person(id=4, name="木村", height="300") # ====== 実行結果 ======= Traceback (most recent call last): File "pydantic_sample4.py", line 55, in <module> person2 = Person(id=4, name="木村", height="300") File "pydantic/main.py", line 331, in pydantic.main.BaseModel.__init__ pydantic.error_wrappers.ValidationError: 1 validation error for Person height ensure this value is less than 300 (type=value_error.number.not_lt; limit_value=300)
メソッドやprivate属性の追加
BaseModel
の派生クラスには、普通のクラスと同様にメソッドを定義したり、privateな属性を持たせたりすることも可能です。
from pydantic import BaseModel, PrivateAttr # 人間クラス class Person(BaseModel): id: int # ユーザID name: str # ユーザ名 height: float # 身長 weight: float # 体重 # 次の方法でprivateな属性を追加できる。 # - `PrivateAttr()`を代入 # - 属性名の先頭にアンダースコアを付与 _bmi: float = PrivateAttr() # __init__のオーバーライドも可能。 def __init__(self, **args): super().__init__(**args) self._bmi = self.weight / (self.height * 0.01) ** 2 # メソッドも普通に追加できる。 def get_bmi(self): return self._bmi # インスタンスの作成 person1 = Person(id=1, name="田中", height=172.0, weight=64.2) # privateな属性は出力されない。 print(person1) #> id=1 name='田中' height=172.0 weight=64.2 print(person1.json()) #> {"id": 1, "name": "田中", "height": 172.0, "weight": 64.2} print(Person.schema_json(indent=2)) """ { "title": "Person", "type": "object", "properties": { "id": { "title": "Id", "type": "integer" }, "name": { "title": "Name", "type": "string" }, "height": { "title": "Height", "type": "number" }, "weight": { "title": "Weight", "type": "number" } }, "required": [ "id", "name", "height", "weight" ] } """
クラスの継承と合成
BaseModel
を継承したクラスのサブクラスや、それらを合成したクラスも適切に機能します。
from datetime import datetime from typing import List, Optional from pydantic import BaseModel # 人間クラス class Person(BaseModel): id: int # ユーザID name: str # ユーザ名 height: Optional[float] # 身長 weight: Optional[float] # 体重 # 試験の得点クラス class Score(BaseModel): exam: str # 試験名 score: int # 点数 date: Optional[datetime] # 実施日 # 生徒クラス class Student(Person): grade: int # 学年 score_list: List[Score] # 試験履歴 # 生徒インスタンスを生成 student1 = Student( id=1, name="田中", height=133.0, grade=4, score_list=[ Score( exam="国語", score=76, ), Score( exam="算数", score=61, date=datetime( year=2022, month=1, day=22 ), ), ], ) # JSONとして出力 print(student1.json(indent=2)) """ { "id": 1, "name": "田中", "height": 133.0, "weight": null, "grade": 4, "score_list": [ { "exam": "国語", "score": 76, "date": null }, { "exam": "算数", "score": 61, "date": "2022-01-22T00:00:00" } ] } """ # 親クラスに変換できる person1_1 = Person.parse_obj(student1) print(person1_1) #> id=1 name='田中' height=133.0 weight=None person1_2 = Person.parse_raw(student1.json()) print(person1_2) #> id=1 name='田中' height=133.0 weight=None
バージョン情報
この記事のスクリプトは以下のバージョンで確認しました。
- Python3.9.1
- pydantic==1.9.0