pydanticの使い方

この記事では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
よかったらシェアしてね!
  • URLをコピーしました!

この記事を書いた人

ITベンチャーでデータ分析、AI開発、システム設計、提案、営業、組織管理、公演、採用などなど多数の役割に従事してきました。

様々な職業や背景の方々と交流するうちに、幅広い分野で問題を解決したり価値を生み出したりするためには、個別の知識だけでなく、汎用的に物事を考える力を伸ばしていく必要があると考えるようになりました。

更に、自分自身の考える力だけでなく、より多くの人々の考える力のトレーニングを応援することで、社会全体を良くしていけるのではないかと考えて、このサイトを作りました。

目次
閉じる