- 技術 💻
Pydanticとは|何に使う・dataclassとの違いと使い方を実測で解説
結論
Pydanticとは、Pythonの型ヒントどおりにデータを検証し、型をそろえるライブラリです。外から届くJSONや設定値の入口に置きます。
この記事でわかること
- 用途7つの早見表とdataclass・attrsとの違い
- 10万件の検証速度を11通りで比べた実測
- v1→v2の移行表19行とエラー原文・直し方
Pydantic はモデルのクラスに書いた型ヒントを「データの決まり」として使い、入力が決まりに合えば型をそろえたオブジェクトを、合わなければ場所と理由の入ったエラーを返します。
flowchart TD
A[外から届くデータ<br>JSON・フォーム・環境変数] --> B[Pydantic のモデル<br>型ヒントが決まり]
B -- 合う --> C[型のそろった<br>オブジェクト]
B -- 合わない --> D[ValidationError<br>どこが・なぜ]
C --> E[model_dump で<br>dict・JSON に戻す]
Pydantic がよく使われる用途です。多い順に並べました。
| 用途 | 使うもの | 例 |
|---|---|---|
| API の入力を検証 | BaseModel+FastAPI | リクエストの JSON |
| JSON を型つきで読む | model_validate_json |
外部 API の応答 |
| 設定・環境変数を読む | pydantic-settings | DB の URL・API キー |
| dict・JSON に書き出す | model_dump / model_dump_json |
レスポンス・保存 |
| LLM の出力を検証 | JSON Schema+検証 | 構造化出力 |
| list・dict をそのまま検証 | TypeAdapter |
CSV の行の一覧 |
| 関数の引数を検証 | validate_call |
ライブラリの公開関数 |
確認環境:Ubuntu 24.04.4 LTS(2 vCPU)・Python 3.14.7 の venv に、PyPI から Pydantic 2.14.1(pydantic-core 2.50.1)・pydantic-settings 2.16.1・FastAPI 0.162.0・attrs 26.1.0・email-validator 2.3.0・bump-pydantic 0.8.0 を入れ、記事のコードをすべて実行して出力をそのまま載せています。uv 0.8.17 の uv add でも同じ版が入ることを確かめました。速さは10万件のデータを9回ずつ交互に測った中央値です。版の履歴は PyPI と GitHub のリリースノート、仕様と用語は Pydantic 公式ドキュメント(pydantic.dev)、FastAPI の動きは FastAPI 公式ドキュメントで確かめました。図は mermaid で描画を確かめています(確認日 2026年10月24日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Pydanticとは|何に使う・dataclassとの違いと使い方を実測で解説」
Pydanticとは(読み方・何をするライブラリか)
この節では、Pydantic が何をするライブラリで、どこが便利なのかが分かります。
Pydantic(パイダンティックと読まれることが多い)は、Python の型ヒントを使ってデータを検証(バリデーション)し、型を変換するライブラリです。 標準ライブラリではないので pip install pydantic で入れます。公式ドキュメントは、検証の決まり(スキーマ)を「主に Python の型ヒントで定義する」と説明しています。
Python の型ヒントは、ふつうは実行時に何もしません。id: int と書いても、文字列を入れればそのまま入ります。Pydantic は、この型ヒントを実行時に読んで、入ってきた値を確かめます。
from datetime import date
from pydantic import BaseModel, ValidationError
class Member(BaseModel):
id: int
name: str
joined: date
# 外から届いたデータ(JSON・フォーム・CSV は文字列で届くことが多い)
data = {"id": "42", "name": "佐藤", "joined": "2026-04-01"}
m = Member.model_validate(data)
print(repr(m))
print(type(m.id).__name__, type(m.joined).__name__)
try:
Member.model_validate({"id": "abc", "name": "佐藤"})
except ValidationError as e:
print(e)
Member(id=42, name='佐藤', joined=datetime.date(2026, 4, 1))
int date
2 validation errors for Member
id
Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='abc', input_type=str]
For further information visit https://errors.pydantic.dev/2.14/v/int_parsing
joined
Field required [type=missing, input_value={'id': 'abc', 'name': '佐藤'}, input_type=dict]
For further information visit https://errors.pydantic.dev/2.14/v/missing
文字列の "42" は int の 42 に、"2026-04-01" は date になりました。間違った入力では、2つの項目の問題がまとめて返っています。
1型ヒントで決まりを書く
クラスに型を書くだけ。エディタの補完や mypy などの型チェッカーともそのまま合う。
2変換して検証する
"42" を 42 に、日付の文字列を date にそろえる。合わない値は場所と理由つきのエラーにする。
3dict・JSON に戻せる
model_dump・model_dump_json で書き出し、JSON Schema も作れる。API と相性がよい。
検証の中心部分は Rust で書かれた pydantic-core が受け持っています。公式ドキュメントの「Why use Pydantic?」のページには、執筆時点で GitHub の 466,400 リポジトリと PyPI の 8,119 パッケージが Pydantic に依存していると書かれ、依存している例として FastAPI・LangChain・Hugging Face の transformers などが挙がっています。
型ヒントそのものの書き方は、関連記事「Pythonの型ヒントの書き方|3.12以降の新しい書き方まで」で扱っています。
Pydanticは何に使う?用途の早見表
この節では、Pydantic が置かれる場所と、置かなくてよい場所が分かります。
Pydantic は「外から来るデータの入口」に置くのが基本です。 外からのデータは、文字列で届く・項目が足りない・余計な項目がある、ということが起きます。入口で1回確かめておけば、内側のコードは型を信じて書けます。
| 用途 | Pydantic がしてくれること | 本文の節 |
|---|---|---|
| Web API の入力 | JSON を読み、型をそろえ、違えば 422 を返す(FastAPI) | FastAPIでの使われ方 |
| 外部 API の応答・ファイル | JSON 文字列を1回で解析と検証 | シリアライズ |
| 設定・環境変数・.env | 文字列を int・bool・list に変換し、足りなければ起動時にエラー | pydantic-settings |
| API の応答・保存 | モデルを dict・JSON に戻す。項目の出し入れも指定できる | シリアライズ |
| LLM の構造化出力 | モデルから JSON Schema を作り、返ってきた JSON を検証 | LLMの構造化出力 |
| CSV の行・一覧の検証 | クラスを作らずに list[dict] などを検証 |
TypeAdapter |
| 関数の引数 | 呼ばれたときに引数を検証する | TypeAdapter・validate_call |
Pydantic を置くところ
API のリクエスト・レスポンス
外部 API・ファイルから読んだ JSON
環境変数・設定ファイル
LLM が返した JSON
標準の dataclass でよいところ
関数の間で値を持ち運ぶだけの入れ物
すでに検証済みの値しか入らないクラス
数百万回作る、速さが大事な内側の処理
依存ライブラリを増やしたくない小さなスクリプト
Pydanticとdataclass・TypedDict・attrsの違い
この節では、似た書き方をする4つの道具が、間違った値をどう扱うかの違いが分かります。
いちばん大きな違いは「実行時に値を確かめて変換するかどうか」です。 標準の dataclass と TypedDict は型ヒントを書いても値を確かめません。Pydantic は確かめて変換し、attrs は書いた分だけ確かめます。
from dataclasses import dataclass
from typing import TypedDict
import attrs
from pydantic import BaseModel, TypeAdapter, ValidationError
from pydantic import dataclasses as pdc
class MemberTD(TypedDict):
id: int
name: str
@pdc.dataclass # Pydantic の検証つき dataclass
class MemberPDC:
id: int
name: str
@dataclass # 標準の dataclass
class MemberDC:
id: int
name: str
@attrs.define # attrs(変換と検証を自分で書く)
class MemberAT:
id: int = attrs.field(converter=int, validator=attrs.validators.instance_of(int))
name: str = attrs.field(validator=attrs.validators.instance_of(str))
class MemberBM(BaseModel):
id: int
name: str
src = {"id": "7", "name": "b"}
print("BaseModel :", repr(MemberBM.model_validate(src)))
print("pydantic dc :", MemberPDC(**src))
print("TypedDict :", TypeAdapter(MemberTD).validate_python(src))
print("dataclass :", MemberDC(**src))
print("attrs :", MemberAT(**src))
print("型ヒントだけ :", MemberTD(**src))
BaseModel : MemberBM(id=7, name='b')
pydantic dc : MemberPDC(id=7, name='b')
TypedDict : {'id': 7, 'name': 'b'}
dataclass : MemberDC(id='7', name='b')
attrs : MemberAT(id=7, name='b')
型ヒントだけ : {'id': '7', 'name': 'b'}
同じクラスに {"id": "x", "name": 1}(どちらも型が違う)を渡した結果も並べます。
BaseModel ValidationError 2件
pydantic dc ValidationError 2件
TypedDict ValidationError 2件
dataclass 通った: MemberDC(id='x', name=1)
attrs ValueError: invalid literal for int() with base 10: 'x'
| 書き方 | "7" を渡すと |
間違った値を渡すと |
|---|---|---|
Pydantic BaseModel |
7(int に変換) | ValidationError(2件まとめて) |
pydantic.dataclasses |
7(int に変換) | ValidationError(2件まとめて) |
TypedDict+TypeAdapter |
7(int に変換) | ValidationError(2件まとめて) |
| 標準の dataclass | '7'(str のまま) | そのまま通る |
| attrs(converter+validator) | 7(書いた converter で変換) | ValueError(最初の1件で止まる) |
| TypedDict(型ヒントだけ) | '7'(ただの dict) | そのまま通る |
dataclass と Pydantic の BaseModel
外から来るデータは BaseModel、検証済みの値を運ぶだけなら dataclass
| 選択肢 | 検証・変換 | 得意なこと | 向いている場面 |
|---|---|---|---|
Pydantic BaseModel |
あり | JSON の入出力・エラー情報・JSON Schema | API の入力・設定・外部データ |
pydantic.dataclasses |
あり | dataclass の書き方のまま検証 | 今の dataclass に検証を足す |
TypeAdapter |
あり | list・dict・TypedDict をそのまま検証 | クラスを作らずに検証 |
| 標準の dataclass | なし | 依存なし・軽い | 内側のデータの受け渡し |
| TypedDict | なし(型チェッカーだけ) | dict のまま型を付ける | JSON を dict で扱うコード |
| attrs | 書いた分だけ | slots・柔軟な設定・converter | ライブラリの内側のクラス |
pydantic.dataclasses には model_dump などのメソッドが付きません(公式ドキュメントの Dataclasses のページ)。手元でも hasattr(MemberPDC, "model_dump") は False で、書き出すときは TypeAdapter(MemberPDC).dump_python(...) を使いました。標準の dataclass の書き方と既定値の注意は、関連記事「Python dataclassの使い方|既定値の罠からslotsまで実行結果で解説」で扱っています。
Pydanticは遅い?dataclass・attrsとの速さの実測
この節では、検証のぶん、どれくらい時間がかかるのかが数字で分かります。
10万件の dict(id は文字列→int に変換、name と price に制約、tags はリスト)を、11通りの方法で作りました。 2 vCPU の共有の環境なので、9回ずつ交互に測って中央値を載せます。
N = 100_000
data = [{"id": str(i), "name": f"item{i}", "price": i % 1000, "tags": ["a", "b"]} for i in range(N)]
raw = json.dumps(data)
class Row(BaseModel):
id: int
name: str = Field(min_length=1)
price: int = Field(ge=0)
tags: list[str] = []
ta_rows = TypeAdapter(list[Row])
[Row.model_validate(d) for d in data] # 1件ずつ
ta_rows.validate_python(data) # まとめて
ta_rows.validate_json(raw) # JSON 文字列から直接
| 方法(10万件) | 検証 | 中央値 |
|---|---|---|
| 標準の dataclass を作る | なし | 48.0 ms |
| attrs を作る | なし | 42.9 ms |
TypeAdapter(list[TypedDict]) |
あり | 78.1 ms |
dataclass+__post_init__ に手書きの検証 |
あり | 113.4 ms |
TypeAdapter(list[Row]) でまとめて |
あり | 191.5 ms |
| attrs(validators+converter) | あり | 214.8 ms |
TypeAdapter.validate_json(JSON 解析込み) |
あり | 280.7 ms |
Row.model_validate を1件ずつ |
あり | 324.5 ms |
pydantic.dataclasses を1件ずつ |
あり | 348.3 ms |
model_construct(検証なしで作る) |
なし | 423.3 ms |
json.loads+model_validate(解析込み) |
あり | 567.1 ms |
分かったことは4つです。
- 検証のない dataclass・attrs が速いのは当然で、Pydantic の1件ずつは約7倍の時間がかかった。そのかわり変換・制約・エラー情報がそろう
- リストは
TypeAdapter(list[...])でまとめて渡すと、1件ずつより約1.7倍速い - JSON 文字列は
json.loadsを挟まずvalidate_jsonに渡すと約2倍速い model_constructは検証を省いても、今回はmodel_validateより遅かった。速さのためより「信頼できるデータを検証なしで作る」ための道具として使う
v1 との比較は、同じ項目と制約のモデルを pydantic.v1(2.14 に同梱の v1 の API)で作り、parse_obj と model_validate を7回ずつ交互に測った中央値です。
Pydanticのインストールと最新版(2.14)
この節では、入れ方と、今の最新版・対応する Python の版が分かります。
2026年10月24日時点の最新は 2.14.1(2026年10月11日公開)です。 2.14.0(2026年10月8日)で Python 3.9 の対応が外れて 3.15 の対応が入り、PyPI の requires_python は >=3.10 です。
| 版 | 公開日(PyPI) | 主なこと |
|---|---|---|
| 2.14.1 | 2026年10月11日 | 2.14 系の最新(2026年10月24日時点) |
| 2.14.0 | 2026年10月8日 | Python 3.9 の対応を終了・3.15 に対応 |
| 2.13.0 | 2026年4月13日 | pydantic.v1 を v1.10.26 に更新(リリースノート) |
| 2.12.0 | 2025年10月7日 | ― |
pip install pydantic # 本体
pip install pydantic-settings # 環境変数・.env を読むとき
pip install "pydantic[email]" # EmailStr を使うとき(email-validator も入る)
uv add pydantic # uv のプロジェクトに足すとき
python -c "import pydantic; print(pydantic.version.version_info())"
pydantic version: 2.14.1
pydantic-core version: 2.50.1
pydantic-core build: profile=release pgo=false
python version: 3.14.7 (main, Sep 24 2026, 17:58:18) [Clang 22.1.3 ]
platform: Linux-6.18.44-fc-v153-x86_64-with-glibc2.39
related packages: fastapi-0.162.0 email-validator-2.3.0 pydantic-settings-2.16.1 typing_extensions-4.16.0
commit: unknown
uv add pydantic では、本体と一緒に4つのパッケージが入りました。
+ annotated-types==0.8.0
+ pydantic==2.14.1
+ pydantic-core==2.50.1
+ typing-extensions==4.16.0
+ typing-inspection==0.4.4
pydantic.version.version_info() の出力は、不具合を報告するときや、環境による違いを調べるときにそのまま使えます。以下のコードは、どれも各節のコードブロックを .py に貼れば動きます。エラーの末尾の https://errors.pydantic.dev/2.14/v/... は、エラーの種類ごとの公式の説明ページです。
BaseModelの使い方と型変換(laxとstrict)
この節では、モデルの作り方と、どの値が自動で変換されるのかが分かります。
BaseModel を継承したクラスに型を書くと、型ヒントどおりに入力を検証し、変換できる値は自動で型をそろえます(lax モード)。
from datetime import date
from pydantic import BaseModel, ValidationError
class Member(BaseModel):
id: int
name: str
joined: date
active: bool = True
m = Member(id="42", name="佐藤", joined="2026-04-01")
print(repr(m))
print(m.id + 1, type(m.joined).__name__)
try:
Member(id="abc", name=123, joined="2026-13-01")
except ValidationError as e:
print(e)
Member(id=42, name='佐藤', joined=datetime.date(2026, 4, 1), active=True)
43 date
3 validation errors for Member
id
Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='abc', input_type=str]
For further information visit https://errors.pydantic.dev/2.14/v/int_parsing
name
Input should be a valid string [type=string_type, input_value=123, input_type=int]
For further information visit https://errors.pydantic.dev/2.14/v/string_type
joined
Input should be a valid date or datetime, month value is outside expected range of 1-12 [type=date_from_datetime_parsing, input_value='2026-13-01', input_type=str]
For further information visit https://errors.pydantic.dev/2.14/v/date_from_datetime_parsing
- 文字列
"42"はintの 42 に、"2026-04-01"はdateに変換された - 数値 123 は
strに変換されずエラーになる。v2 は「数値→文字列」の自動変換をしない(v1 との違い) - エラーは最初の1件で止まらず、全項目まとめて返る
lax(変換する)と strict(変換しない)の違い
同じ値を、既定の lax と strict で検証した結果です。 TypeAdapter(型).validate_python(値, strict=...) で全行を実行しました。
| 入力 → 型 | lax(既定) | strict |
|---|---|---|
"42" → int |
42 | エラー(int_type) |
42.0 → int |
42 | エラー(int_type) |
42.5 → int |
エラー(int_from_float) | エラー(int_type) |
123 → str |
エラー(string_type) | エラー(string_type) |
"true" / "yes" / 1 → bool |
True | エラー(bool_type) |
"3.14" → float |
3.14 | エラー(float_type) |
"2026-04-01" → date |
date(2026, 4, 1) | エラー(date_type) |
[1, 2] → tuple |
(1, 2) | エラー(tuple_type) |
("1", "2") → list[int] |
[1, 2] | エラー(list_type) |
from typing import Annotated
from pydantic import BaseModel, ConfigDict, Field
class StrictMember(BaseModel):
model_config = ConfigDict(strict=True) # モデル全体を strict に
id: int
name: str
class PartStrict(BaseModel):
id: Annotated[int, Field(strict=True)] # この項目だけ strict
qty: int
StrictMember(id="42", name="佐藤") # → エラー
PartStrict(id=1, qty="3") # → PartStrict(id=1, qty=3)
PartStrict(id="1", qty="3") # → エラー
1 validation error for StrictMember
id
Input should be a valid integer [type=int_type, input_value='42', input_type=str]
For further information visit https://errors.pydantic.dev/2.14/v/int_type
PartStrict(id=1, qty=3)
1 validation error for PartStrict
id
Input should be a valid integer [type=int_type, input_value='1', input_type=str]
For further information visit https://errors.pydantic.dev/2.14/v/int_type
目安は、外から文字列で届く値(フォーム・クエリ文字列・CSV)は lax、内側の関数の間で型を厳密に守りたいなら strict です。 JSON には日付の型が無いため、JSON 文字列から読むときは strict でも "2026-04-01" を date として受け付けます(TypeAdapter(date).validate_json('"2026-04-01"', strict=True) は 2026-04-01 を返しました)。
Fieldで制約をつける(Annotatedで使い回す)
この節では、数値の範囲・文字数・正規表現などの制約の付け方が分かります。
from pydantic import BaseModel, Field
class Item(BaseModel):
name: str = Field(min_length=1, max_length=20)
price: int = Field(gt=0, le=100_000)
code: str = Field(pattern=r"^[A-Z]{3}-\d{4}$")
tags: list[str] = Field(default_factory=list)
print(repr(Item(name="ペン", price=120, code="STA-0001")))
Item(name="", price=0, code="sta-1") # → エラー
Item(name='ペン', price=120, code='STA-0001', tags=[])
3 validation errors for Item
name
String should have at least 1 character [type=string_too_short, input_value='', input_type=str]
For further information visit https://errors.pydantic.dev/2.14/v/string_too_short
price
Input should be greater than 0 [type=greater_than, input_value=0, input_type=int]
For further information visit https://errors.pydantic.dev/2.14/v/greater_than
code
String should match pattern '^[A-Z]{3}-\d{4}$' [type=string_pattern_mismatch, input_value='sta-1', input_type=str]
For further information visit https://errors.pydantic.dev/2.14/v/string_pattern_mismatch
| 引数 | 意味 | 対象 |
|---|---|---|
gt / ge |
より大きい/以上 | 数値 |
lt / le |
より小さい/以下 | 数値 |
multiple_of |
倍数(2.14 から正の値だけ) | 数値 |
min_length / max_length |
長さ | 文字列・リスト |
pattern |
正規表現 | 文字列 |
default / default_factory |
既定値/既定値を作る関数 | すべて |
alias |
入出力のキー名 | すべて |
description / examples |
説明(JSON Schema に出る) | すべて |
exclude=True |
model_dump に出さない |
すべて |
strict=True |
この項目だけ strict | すべて |
Annotatedで制約つきの型を使い回す
同じ制約を何度も書くなら、Annotated で型に名前を付けます。
from typing import Annotated
from pydantic import BaseModel, Field
Price = Annotated[int, Field(gt=0, le=100_000, description="税込価格")]
Code = Annotated[str, Field(pattern=r"^[A-Z]{3}-\d{4}$")]
class Stock(BaseModel):
code: Code
unit_price: Price
qty: Annotated[int, Field(ge=0)] = 0
Stock(code="STA-0001", unit_price=200_000) # → エラー
1 validation error for Stock
unit_price
Input should be less than or equal to 100000 [type=less_than_equal, input_value=200000, input_type=int]
For further information visit https://errors.pydantic.dev/2.14/v/less_than_equal
Optionalと既定値(必須かどうかの決まり方)
この節では、項目が必須になる条件と、None を許す書き方の違いが分かります。
v2 では「既定値があるかどうか」だけで必須かが決まります。 str | None と書いても、既定値が無ければ必須です。
from pydantic import BaseModel
class Profile(BaseModel):
a: str | None # 必須(None は許可)
b: str | None = None # 省略可、既定 None
c: str = "x" # 省略可
Profile() # → エラー
Profile(a=None) # → Profile(a=None, b=None, c='x')
1 validation error for Profile
a
Field required [type=missing, input_value={}, input_type=dict]
For further information visit https://errors.pydantic.dev/2.14/v/missing
Profile(a=None, b=None, c='x')
| 書き方 | 省略すると | None を渡すと |
|---|---|---|
a: str |
エラー | エラー |
a: str | None |
エラー(v1 では省略可だった) | 通る |
a: str | None = None |
通る(None) | 通る |
a: str = "x" |
通る("x") | エラー |
list = [] のような変更できる既定値は、インスタンスごとに別のものになります。 手元でも、1つ目のインスタンスのリストに "pen" を足したとき、2つ目は [] のままでした(['pen'] [])。関数で既定値を作りたいときは default_factory を使います。
ネストしたモデルとエラーの読み方(errors())
この節では、モデルの中にモデルを入れたときの検証と、エラーの場所の読み方が分かります。
from pydantic import BaseModel, Field
class Address(BaseModel):
zip: str = Field(pattern=r"^\d{3}-\d{4}$")
city: str
class Customer(BaseModel):
name: str
address: Address
history: list[Address] = []
c = Customer.model_validate({"name": "田中", "address": {"zip": "100-0001", "city": "千代田区"}})
print(repr(c.address))
Customer.model_validate({"name": "田中", "address": {"zip": "1000001"},
"history": [{"zip": "100-0001", "city": "A"}, {"city": "B"}]})
Address(zip='100-0001', city='千代田区')
3 validation errors for Customer
address.zip
String should match pattern '^\d{3}-\d{4}$' [type=string_pattern_mismatch, input_value='1000001', input_type=str]
For further information visit https://errors.pydantic.dev/2.14/v/string_pattern_mismatch
address.city
Field required [type=missing, input_value={'zip': '1000001'}, input_type=dict]
For further information visit https://errors.pydantic.dev/2.14/v/missing
history.1.zip
Field required [type=missing, input_value={'city': 'B'}, input_type=dict]
For further information visit https://errors.pydantic.dev/2.14/v/missing
内側のエラーも address.zip、history.1.zip(リストの2番目)のように場所つきで返ります。 画面のどの入力欄にエラーを出すかを、この場所から決められます。
errors() でエラーをプログラムで扱う
from datetime import date
class Line(BaseModel):
unit_price: int
qty: int
class Order(BaseModel):
id: int
ordered_on: date
note: str | None = None
lines: list[Line]
try:
Order.model_validate({"id": "x", "ordered_on": "2026-10-01", "lines": [{"unit_price": 1}]})
except ValidationError as e:
print(e.error_count())
for err in e.errors(include_url=False):
print(err["loc"], err["type"], err["msg"])
2
('id',) int_parsing Input should be a valid integer, unable to parse string as an integer
('lines', 0, 'qty') missing Field required
API のエラーレスポンスには e.errors() のリスト(loc・type・msg)を返す形がよく使われます。 FastAPI の 422 エラーもこの形です(後の節で実物を載せています)。
field_validatorとmodel_validator(独自の検証)
この節では、型と Field で書けない決まりを、自分で足す方法と、呼ばれる順番が分かります。
1つの項目で完結する決まりは field_validator、2つ以上の項目の関係は model_validator に書きます。 4種類を1つのモデルに置き、どの順で呼ばれるかを確かめました。
flowchart TD
A[生の入力 dict・JSON] --> B[model_validator before]
B --> C[field_validator before]
C --> D[型の検証・変換と<br>Field の制約]
D --> E[field_validator after]
E --> F[model_validator after]
1 model_validator(before): {'qty': '3'}
2 field_validator(before): '3'
3 field_validator(after) : 3
4 model_validator(after) : M(qty=3)
1 model_validator(before): {'qty': '0'}
2 field_validator(before): '0'
ValidationError greater_than_equal
qty: int = Field(ge=1) に "0" を渡すと、Field の制約で止まり、after の2つは呼ばれませんでした。after の中では「型と制約はもう通っている」と考えて書けます。
field_validator:1項目ずつの検証・整形
from pydantic import BaseModel, field_validator
class Signup(BaseModel):
email: str
nickname: str
@field_validator("email") # 既定は mode="after"(型変換の後)
@classmethod
def normalize_email(cls, v: str) -> str:
if "@" not in v:
raise ValueError("メールアドレスに @ がありません")
return v.strip().lower() # 返した値が最終的な値になる
@field_validator("nickname", mode="before") # 型変換の前に呼ばれる
@classmethod
def strip_nickname(cls, v):
return v.strip() if isinstance(v, str) else v
print(repr(Signup(email=" Hana@Example.COM ", nickname=" hana ")))
Signup(email="hana.example.com", nickname="hana") # → エラー
Signup(email='hana@example.com', nickname='hana')
1 validation error for Signup
email
Value error, メールアドレスに @ がありません [type=value_error, input_value='hana.example.com', input_type=str]
For further information visit https://errors.pydantic.dev/2.14/v/value_error
model_validator:複数項目の関係を確かめる
from datetime import date
from typing import Self
from pydantic import BaseModel, ValidationInfo, field_validator, model_validator
class Booking(BaseModel):
start: date
end: date
@model_validator(mode="after") # 全項目の検証が済んだ後。self を受け取る
def check(self) -> Self:
if self.end < self.start:
raise ValueError("end は start 以降の日付にします")
return self
class Booking2(BaseModel): # 同じ決まりを end の項目のエラーにする
start: date
end: date
@field_validator("end")
@classmethod
def after_start(cls, v: date, info: ValidationInfo) -> date:
if "start" in info.data and v < info.data["start"]:
raise ValueError("end は start 以降の日付にします")
return v
class Legacy(BaseModel):
full_name: str
@model_validator(mode="before") # 生の入力(dict など)を受け取って形を変える
@classmethod
def from_old_format(cls, data):
if isinstance(data, dict) and "first" in data:
return {"full_name": f"{data['last']} {data['first']}"}
return data
Booking(start="2026-10-05", end="2026-10-01") # → エラー
Booking2(start="2026-10-05", end="2026-10-01") # → エラー
print(repr(Legacy.model_validate({"first": "花子", "last": "山田"})))
1 validation error for Booking
Value error, end は start 以降の日付にします [type=value_error, input_value={'start': '2026-10-05', 'end': '2026-10-01'}, input_type=dict]
For further information visit https://errors.pydantic.dev/2.14/v/value_error
1 validation error for Booking2
end
Value error, end は start 以降の日付にします [type=value_error, input_value='2026-10-01', input_type=str]
For further information visit https://errors.pydantic.dev/2.14/v/value_error
Legacy(full_name='山田 花子')
model_validator のエラーは項目名の行が空(loc が空)になります。 画面の特定の欄にエラーを出したいときは、Booking2 のように field_validator で info.data(先に検証が済んだ項目)を見ると、end のエラーとして返せます。
| デコレーター | mode | 受け取るもの | 使う場面 |
|---|---|---|---|
@field_validator |
"after"(既定) |
変換済みの値 | 整形・追加の確認 |
@field_validator |
"before" |
生の値 | 前処理(空白を除く・カンマ区切りをリストに) |
@field_validator |
"wrap" |
値と次の処理 | 例外を受けて既定値にする等 |
@model_validator |
"after" |
self |
項目どうしの関係(開始≦終了) |
@model_validator |
"before" |
生の入力全体 | 前の形式からの変換 |
シリアライズ:model_dump・JSON・エイリアス
この節では、モデルを dict・JSON に戻す方法と、JSON のキー名を Python の名前と分ける方法が分かります。
from datetime import date
from pydantic import BaseModel, computed_field
class Line(BaseModel):
unit_price: int
qty: int
@computed_field
@property
def subtotal(self) -> int:
return self.unit_price * self.qty
class Order(BaseModel):
id: int
ordered_on: date
note: str | None = None
lines: list[Line]
l = Line(unit_price=120, qty=3)
print(l.subtotal, l.model_dump())
o = Order(id=1, ordered_on="2026-10-01", lines=[{"unit_price": 100, "qty": 2}])
print(o.model_dump())
print(o.model_dump(mode="json"))
print(o.model_dump(exclude_none=True, include={"id", "note"}))
print(o.model_dump_json())
print(o.model_dump_json(indent=2, exclude={"lines"}))
print(repr(Order.model_validate_json('{"id": 2, "ordered_on": "2026-10-02", "lines": []}')))
print(repr(o.model_copy(update={"note": "至急"})))
360 {'unit_price': 120, 'qty': 3, 'subtotal': 360}
{'id': 1, 'ordered_on': datetime.date(2026, 10, 1), 'note': None, 'lines': [{'unit_price': 100, 'qty': 2, 'subtotal': 200}]}
{'id': 1, 'ordered_on': '2026-10-01', 'note': None, 'lines': [{'unit_price': 100, 'qty': 2, 'subtotal': 200}]}
{'id': 1}
{"id":1,"ordered_on":"2026-10-01","note":null,"lines":[{"unit_price":100,"qty":2,"subtotal":200}]}
{
"id": 1,
"ordered_on": "2026-10-01",
"note": null
}
Order(id=2, ordered_on=datetime.date(2026, 10, 2), note=None, lines=[])
Order(id=1, ordered_on=datetime.date(2026, 10, 1), note='至急', lines=[Line(unit_price=100, qty=2, subtotal=200)])
model_dump() は date を Python のオブジェクトのまま返し、mode="json" を付けると文字列になります。 そのまま json.dumps() に渡すなら mode="json"、JSON 文字列が欲しいなら model_dump_json() が近道です。computed_field で定義した subtotal は出力にも入ります。
| メソッド | 入力 → 出力 | 主なオプション |
|---|---|---|
model_validate(obj) |
dict・オブジェクト → モデル | strict・from_attributes |
model_validate_json(s) |
JSON 文字列 → モデル | strict |
model_dump() |
モデル → dict | mode・include・exclude・by_alias・exclude_none・exclude_unset |
model_dump_json() |
モデル → JSON 文字列 | indent ほか上と同じ |
model_copy(update=...) |
モデル → 新しいモデル | deep=True(update の値は検証されない) |
model_json_schema() |
クラス → JSON Schema(dict) | by_alias・mode |
model_construct(**kw) |
検証せずに作る | 信頼できるデータだけ |
出力の形を項目ごとに変えるときは field_serializer を使います。datetime(2026, 10, 24, 9, 30) を "%Y/%m/%d %H:%M" で書き出すと、{'at': '2026/10/24 09:30'} になりました。JSON の書き方の決まりは、関連記事「JSONとは|JSONファイルの書き方・開き方とエラー例を実物で解説」で扱っています。
エイリアス(JSON のキー名と Python の名前を分ける)
JSON が userId のような camelCase で、Python では user_id と書きたいときはエイリアスを使います。 2.11 から、入力で項目名を許す validate_by_name と、出力をエイリアスにする serialize_by_alias を分けて指定できます。
from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel
class ApiUser(BaseModel):
model_config = ConfigDict(validate_by_name=True, serialize_by_alias=True)
user_id: int = Field(alias="userId")
display_name: str = Field(alias="displayName")
u = ApiUser.model_validate({"userId": 1, "displayName": "Hana"})
print(repr(u))
print(u.model_dump(), u.model_dump(by_alias=False))
print(repr(ApiUser(user_id=2, display_name="Ken"))) # Python の名前でも作れる
class CamelModel(BaseModel): # 全項目を自動で camelCase に
model_config = ConfigDict(alias_generator=to_camel, validate_by_name=True)
order_id: int
created_by: str
print(CamelModel.model_validate({"orderId": 5, "createdBy": "me"}).model_dump(by_alias=True))
ApiUser(user_id=1, display_name='Hana')
{'userId': 1, 'displayName': 'Hana'} {'user_id': 1, 'display_name': 'Hana'}
ApiUser(user_id=2, display_name='Ken')
{'orderId': 5, 'createdBy': 'me'}
前からある populate_by_name=True も、2.14.1 では警告なしで動きました。
model_configの設定(strict・frozen・extra など)
この節では、モデル全体の動きを変える設定と、変えたときのエラーが分かります。
from pydantic import BaseModel, ConfigDict, Field
class Task(BaseModel):
model_config = ConfigDict(validate_assignment=True)
title: str
priority: int = Field(ge=1, le=5)
t = Task(title="棚卸し", priority=3)
t.priority = 9 # → エラー
class Point(BaseModel):
model_config = ConfigDict(frozen=True)
x: int
Point(x=1).x = 2 # → エラー
class StrictKeys(BaseModel):
model_config = ConfigDict(extra="forbid")
name: str
StrictKeys(name="a", age=3) # → エラー
class Loose(BaseModel): # 既定の extra="ignore"
name: str
print(repr(Loose(name="a", age=3)))
1 validation error for Task
priority
Input should be less than or equal to 5 [type=less_than_equal, input_value=9, input_type=int]
For further information visit https://errors.pydantic.dev/2.14/v/less_than_equal
1 validation error for Point
x
Instance is frozen [type=frozen_instance, input_value=2, input_type=int]
For further information visit https://errors.pydantic.dev/2.14/v/frozen_instance
1 validation error for StrictKeys
age
Extra inputs are not permitted [type=extra_forbidden, input_value=3, input_type=int]
For further information visit https://errors.pydantic.dev/2.14/v/extra_forbidden
Loose(name='a')
既定の extra="ignore" では、余分な age は黙って捨てられました。
| 設定 | 既定 | 変えると |
|---|---|---|
extra |
"ignore" |
"forbid" で余分なキーをエラー、"allow" で残す |
validate_assignment |
False | 代入したときも検証 |
frozen |
False | 変更できない・ハッシュできる |
strict |
False | 型を変換しない |
from_attributes |
False | ORM のオブジェクトなど属性から作れる |
coerce_numbers_to_str |
False | 数値→文字列の変換を許す(v1 と同じ動き) |
str_strip_whitespace |
False | 文字列の前後の空白を除く |
use_enum_values |
False | Enum を値で持つ |
from_attributes=True を付けると、ORM の行のような「属性を持つオブジェクト」からモデルを作れます。手元では、id・name・password_hash を持つオブジェクトから UserOut(id=1, name='佐藤') を作れました(モデルに無い password_hash は入りません)。付けないと次のエラーになります。
1 validation error for NoAttr
Input should be a valid dictionary or instance of NoAttr [type=model_type, input_value=<__main__.UserRow object at 0x7fe03e2dec10>, input_type=UserRow]
For further information visit https://errors.pydantic.dev/2.14/v/model_type
TypeAdapter・validate_call・よく使う型
この節では、クラスを作らずに検証する方法と、メールアドレスや URL などの型が分かります。
from pydantic import BaseModel, ConfigDict, TypeAdapter, ValidationError, validate_call, EmailStr, HttpUrl, PositiveInt
# TypeAdapter:クラスを作らずに list・dict を検証
print(TypeAdapter(list[int]).validate_python(["1", 2, "3"]))
print(TypeAdapter(dict[str, float]).validate_json('{"a": "1.5", "b": 2}'))
# validate_call:関数の引数を検証
@validate_call
def repeat(text: str, times: PositiveInt) -> str:
return text * times
print(repeat("ab", "3"))
repeat("ab", 0) # → エラー
# よく使う型(EmailStr は pydantic[email] が要る)
class Contact(BaseModel):
email: EmailStr
site: HttpUrl
print(repr(Contact(email="Hana@Example.com", site="https://example.com/a?b=1")))
Contact(email="hana@", site="example.com") # → エラー
# 数値→文字列を許す(v1 と同じ動き)
class Code(BaseModel):
model_config = ConfigDict(coerce_numbers_to_str=True)
code: str
print(repr(Code(code=123)))
[1, 2, 3]
{'a': 1.5, 'b': 2.0}
ababab
1 validation error for repeat
1
Input should be greater than 0 [type=greater_than, input_value=0, input_type=int]
For further information visit https://errors.pydantic.dev/2.14/v/greater_than
Contact(email='Hana@example.com', site=HttpUrl('https://example.com/a?b=1'))
2 validation errors for Contact
email
value is not a valid email address: There must be something after the @-sign. [type=value_error, input_value='hana@', input_type=str]
site
Input should be a valid URL, relative URL without a base [type=url_parsing, input_value='example.com', input_type=str]
For further information visit https://errors.pydantic.dev/2.14/v/url_parsing
Code(code='123')
EmailStr はドメインの部分を小文字にそろえました(Hana@Example.com → Hana@example.com)。validate_call のエラーの場所 1 は、2番目の引数(0 から数える)を指しています。
pydantic-settingsで環境変数と.envを読む
この節では、環境変数や .env を型つきで読み、足りない値を起動時に見つける方法が分かります。
v2 では BaseSettings が別のパッケージ pydantic-settings に分かれました。 環境変数は文字列でしか届きませんが、モデルの型どおりに int・bool・list に変換されます。
# settings_demo.py
import os
from pydantic import SecretStr, ValidationError
from pydantic_settings import BaseSettings, SettingsConfigDict
with open(".env", "w") as f: # デモ用に .env を作る
f.write("APP_DB_URL=postgresql://localhost/app\nAPP_DEBUG=true\nAPP_API_KEY=abc123\nAPP_PORT=7000\n")
os.environ["APP_PORT"] = "8080"
os.environ["APP_ALLOWED_HOSTS"] = '["a.example.com","b.example.com"]'
class Settings(BaseSettings):
model_config = SettingsConfigDict(env_prefix="APP_", env_file=".env")
db_url: str
port: int = 8000
debug: bool = False
api_key: SecretStr # print しても伏せ字になる
allowed_hosts: list[str] = [] # list は JSON の形で渡す
s = Settings()
print(s)
print(s.port + 1, s.api_key.get_secret_value())
print(Settings(port=9000).port) # 引数が最優先
os.environ["APP_PORT"] = "eighty"
Settings() # → エラー
os.environ["APP_PORT"] = "8080"
os.environ["APP_ALLOWED_HOSTS"] = "a.example.com,b.example.com"
Settings() # → エラー(JSON の形でない)
db_url='postgresql://localhost/app' port=8080 debug=True api_key=SecretStr('**********') allowed_hosts=['a.example.com', 'b.example.com']
8081 abc123
9000
1 validation error for Settings
port
Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='eighty', input_type=str]
For further information visit https://errors.pydantic.dev/2.14/v/int_parsing
SettingsError : error parsing value for field "allowed_hosts" from source "EnvSettingsSource"
優先の順は「初期化の引数 > 環境変数 > .env > 既定値」です。 .env の APP_PORT=7000 より環境変数の 8080 が、それより引数の 9000 が使われました。公式ドキュメントでは、環境変数の名前は既定で大文字・小文字を区別しないこと、list・dict などは「値を JSON の文字列として」読むこと、env_nested_delimiter="__" で入れ子のモデルに分けられることが説明されています。
FastAPIでのPydanticの使われ方
この節では、FastAPI がリクエストを Pydantic で検証し、エラーを返す様子が実物で分かります。
FastAPI では、関数の引数に BaseModel を書くと、リクエストの本文の JSON を読み、型を変換し、検証し、合わなければ 422 を返します。 公式ドキュメントは、これに加えてエディタの補完と、JSON Schema(OpenAPI)の自動生成を挙げています。
本文の JSON を読む
リクエストの本文を JSON として解析する。
モデルで検証・変換
"120" は 120 に。合わなければ errors() と同じ形の 422 を返す。
関数を呼ぶ
引数には検証済みのモデルが入る。中の処理は型を信じて書ける。
応答の形をそろえる
response_model の項目だけを返す。モデルに無い項目は出ない。
import json
from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel, Field
class ItemIn(BaseModel):
name: str = Field(min_length=1, max_length=20)
price: int = Field(gt=0)
tags: list[str] = []
class ItemOut(BaseModel):
id: int
name: str
price: int
app = FastAPI()
@app.post("/items", response_model=ItemOut, status_code=201)
def create_item(item: ItemIn):
# item はすでに検証済みの ItemIn。price は int になっている
return {"id": 1, "name": item.name, "price": item.price, "secret": "返さない"}
c = TestClient(app)
r = c.post("/items", json={"name": "ペン", "price": "120"})
print(r.status_code, r.json())
r = c.post("/items", json={"name": "", "price": 0})
for d in r.json()["detail"]: print(r.status_code, json.dumps(d, ensure_ascii=False))
201 {'id': 1, 'name': 'ペン', 'price': 120}
422 {"type": "string_too_short", "loc": ["body", "name"], "msg": "String should have at least 1 character", "input": "", "ctx": {"min_length": 1}}
422 {"type": "greater_than", "loc": ["body", "price"], "msg": "Input should be greater than 0", "input": 0, "ctx": {"gt": 0}}
"120" は 120 として受け取られ、関数が返した secret は response_model に無いので応答に出ませんでした。422 の detail は、loc の先頭に body が付くほかは e.errors() と同じ形です。/openapi.json には、ItemIn の制約が "minLength": 1・"exclusiveMinimum": 0.0 などの JSON Schema として入っていました。
FastAPI で CRUD の API を作る手順は、関連記事「FastAPI入門|uvで作るCRUD API完成コードと実行結果」で、Pydantic のモデルをそのままデータベースのテーブルにも使う方法は、関連記事「SQLModel入門|FastAPIで使うモデル定義とCRUD」で扱っています。
LLMの構造化出力とJSON Schema
この節では、Pydantic のモデルを LLM の出力の「型」として使う流れが分かります。
モデルから JSON Schema を作って LLM に渡し、返ってきた JSON 文字列を model_validate_json で検証します。 公式ドキュメントによると、出力される JSON Schema は 2020-12 の仕様に沿っていて、OpenAPI 3.1 と互換です。
import json
from typing import Literal
from pydantic import BaseModel, Field, ValidationError
class Inquiry(BaseModel):
"""問い合わせメールから取り出す項目"""
category: Literal["見積もり", "不具合", "その他"]
company: str = Field(description="会社名")
budget_yen: int | None = Field(default=None, ge=0, description="予算(円)")
print(json.dumps(Inquiry.model_json_schema(), ensure_ascii=False))
# LLM が返した(ことにする)文字列を検証する
print(repr(Inquiry.model_validate_json('{"category": "見積もり", "company": "サンプル商事", "budget_yen": "300000"}')))
Inquiry.model_validate_json('{"category": "相談", "company": "サンプル商事", "budget_yen": -1') # 途中で切れた JSON
Inquiry.model_validate_json('{"category": "相談", "company": "サンプル商事", "budget_yen": -1}') # 値が決まりと違う
{"description": "問い合わせメールから取り出す項目", "properties": {"category": {"enum": ["見積もり", "不具合", "その他"], "title": "Category", "type": "string"}, "company": {"description": "会社名", "title": "Company", "type": "string"}, "budget_yen": {"anyOf": [{"minimum": 0, "type": "integer"}, {"type": "null"}], "default": null, "description": "予算(円)", "title": "Budget Yen"}}, "required": ["category", "company"], "title": "Inquiry", "type": "object"}
Inquiry(category='見積もり', company='サンプル商事', budget_yen=300000)
1 validation error for Inquiry
Invalid JSON: EOF while parsing an object at line 1 column 72 [type=json_invalid, input_value='{"category": "相談", "...事", "budget_yen": -1', input_type=str]
For further information visit https://errors.pydantic.dev/2.14/v/json_invalid
2 validation errors for Inquiry
category
Input should be '見積もり', '不具合' or 'その他' [type=literal_error, input_value='相談', input_type=str]
For further information visit https://errors.pydantic.dev/2.14/v/literal_error
budget_yen
Input should be greater than or equal to 0 [type=greater_than_equal, input_value=-1, input_type=int]
For further information visit https://errors.pydantic.dev/2.14/v/greater_than_equal
クラスの docstring と Field(description=...) は、JSON Schema の description に入ります。LLM に項目の意味を伝える場所として使えます。途中で切れた JSON は json_invalid、決まりと違う値は項目ごとのエラーになるので、エラーの内容を添えて LLM に再生成を頼む、という作りにできます。各社の API の構造化出力の方式の違いは、関連記事「LLMの構造化出力とJSON|4方式の違いとPydantic検証の実行結果」で扱っています。
Pydantic v1からv2への移行(対応表)
この節では、v1 のコードを v2 に書き換えるときの対応と、移行ツールの実行結果が分かります。
v1 の .dict() などは v2 でも動きますが、PydanticDeprecatedSince20 の警告が出ます。
ex11_v1.py:6: PydanticDeprecatedSince20: The `dict` method is deprecated; use `model_dump` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.14/migration/
ex11_v1.py:7: PydanticDeprecatedSince20: The `parse_obj` method is deprecated; use `model_validate` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.14/migration/
| v1 | v2 |
|---|---|
Model.parse_obj(d) |
Model.model_validate(d) |
Model.parse_raw(s) |
Model.model_validate_json(s) |
m.dict() |
m.model_dump() |
m.json() |
m.model_dump_json() |
m.copy() |
m.model_copy() |
Model.schema() |
Model.model_json_schema() |
Model.construct() |
Model.model_construct() |
Model.update_forward_refs() |
Model.model_rebuild() |
Model.__fields__ |
Model.model_fields |
parse_obj_as(list[M], d) |
TypeAdapter(list[M]).validate_python(d) |
@validator |
@field_validator(@classmethod を付ける) |
@root_validator |
@model_validator |
class Config: |
model_config = ConfigDict(...) |
orm_mode = True |
from_attributes=True |
allow_population_by_field_name |
populate_by_name(2.11 から validate_by_name も) |
Field(regex=...) |
Field(pattern=...) |
min_items / max_items |
min_length / max_length |
from pydantic import BaseSettings |
from pydantic_settings import BaseSettings |
Optional[X] は省略できた |
既定値が無ければ必須 |
bump-pydantic で書き換えた結果
公式の移行ツール bump-pydantic(ベータ版)は、pip install bump-pydantic で入れ、bump-pydantic パッケージのフォルダ で実行します。 v1 の書き方のファイルに通した前と後です。
# 前(v1 の書き方)
from typing import Optional
from pydantic import BaseModel, validator
class User(BaseModel):
id: int
name: str
note: Optional[str]
class Config:
orm_mode = True
@validator("name")
def strip_name(cls, v):
return v.strip()
def load(d):
u = User.parse_obj(d)
return u.dict()
# 後(bump-pydantic 0.8.0 が書き換えたもの)
from typing import Optional
from pydantic import field_validator, ConfigDict, BaseModel
class User(BaseModel):
id: int
name: str
note: Optional[str] = None
model_config = ConfigDict(from_attributes=True)
@field_validator("name")
@classmethod
def strip_name(cls, v):
return v.strip()
def load(d):
u = User.parse_obj(d)
return u.dict()
Config・@validator・Optional の既定値は書き換わり、parse_obj と .dict() の呼び出しはそのまま残りました。実行すると、この2行に PydanticDeprecatedSince20 の警告が出ます。ツールの後は、警告を手がかりに上の表で書き換えます。
- 1bump-pydantic を通して差分を確かめる--diff で書き換える前に中身を見られる
- 2警告を表示してテストを流すpython -W always で PydanticDeprecatedSince20 を全部出す
- 3Optional で既定値の無い項目を探すv2 では必須になる
- 4数値を str の項目に入れている所を探すv2 はエラー。coerce_numbers_to_str で前と同じにできる
- 5BaseSettings の import を直すpydantic-settings を入れて import 先を変える
一度に書き換えられないときは、from pydantic.v1 import BaseModel で v1 の動きのまま使えます。 2.13.0 のリリースノートでは、pydantic.v1 を Python 3.14 に対応した v1.10.26 に更新したと書かれています。手元の Python 3.14.7 でも、pydantic.v1 の @validator を使ったモデルが動きました({'id': 3, 'name': '123'}。v1 では数値 123 が文字列になる)。
よくあるエラーと直し方
この節では、Pydantic でよく出るエラーの原文と、原因・直し方が分かります。
| エラー・症状 | 原因 | 直し方 |
|---|---|---|
Field required [type=missing] |
X | None に既定値が無い |
= None を付ける |
Input should be a valid string [type=string_type] |
数値を str に渡した(v2 は変換しない) | 呼ぶ側で str()、または coerce_numbers_to_str=True |
Input should be a valid integer, unable to parse string |
数字でない文字列 | 入力を直す。前処理は field_validator(mode="before") |
Extra inputs are not permitted |
extra="forbid" で決めていないキー |
モデルに項目を足すか、キーを除く |
Instance is frozen |
frozen=True のモデルに代入 |
model_copy(update=...) で新しく作る |
Input should be a valid dictionary or instance of ... |
ORM のオブジェクトをそのまま渡した | from_attributes=True |
Invalid JSON: EOF while parsing |
JSON 文字列が途中で切れている | 送る側を直す。LLM なら再生成 |
Decorators defined with incorrect fields |
@field_validator("無い項目名") |
項目名を直す(継承先で定義するなら check_fields=False) |
BaseModel.__init__() takes 1 positional argument |
Line(120, 3) と位置で渡した |
Line(unit_price=120, qty=3) とキーワードで渡す |
BaseSettings has been moved |
v1 の import | pydantic-settings を入れて import 先を変える |
regex is removed. use pattern instead |
v1 の Field(regex=...) |
Field(pattern=...) |
If you use @root_validator with pre=False ... |
v1 の @root_validator |
@model_validator(mode="after") |
'orm_mode' has been renamed の警告 |
v1 の設定名 | from_attributes=True |
shadows an attribute in parent "BaseModel" の警告 |
項目名に json などを使った |
別の名前にして alias="json" を付ける |
SettingsError: error parsing value for field |
list の環境変数が JSON の形でない | '["a","b"]' の形で渡す |
PydanticUserError : Decorators defined with incorrect fields: __main__.a.<locals>.Bad:1111272448.chk (use check_fields=False if you're inheriting from the model and intended this)
TypeError : BaseModel.__init__() takes 1 positional argument but 3 were given
UserWarning: Valid config keys have changed in V2:
* 'orm_mode' has been renamed to 'from_attributes'
PydanticImportError : `BaseSettings` has been moved to the `pydantic-settings` package. See https://pydantic.dev/docs/validation/2.14/migration/#basesettings-has-moved-to-pydantic-settings for more details.
UserWarning : Field name "json" in "M2" shadows an attribute in parent "BaseModel"
PydanticUserError : `regex` is removed. use `pattern` instead
PydanticUserError : If you use `@root_validator` with pre=False (the default) you MUST specify `skip_on_failure=True`. Note that `@root_validator` is deprecated and should be replaced with `@model_validator`.
検証が効かない落とし穴と、作り直し方
この節では、Pydantic のモデルなのに決まりに合わない値が入ってしまう場面と、その直し方が分かります。
次の3つは、検証を通らずに値が入ります。 priority: int = Field(ge=1, le=5) のモデルで試しました。
t = Task(priority=3)
t.priority = "高" # 既定では代入は検証されない
u = Task.model_construct(priority=-5) # 検証しないで作る
v = t.model_copy(update={"priority": 99}) # update の値も検証されない
w = Task.model_validate({**v.model_dump(), "priority": 4}) # 作り直すと検証される
Task(priority='高')
Task(priority=-5)
Task(priority=99)
Task(priority=4)
| 操作 | 何が起きるか | 代わりの書き方 |
|---|---|---|
| 作った後の代入 | 型も制約も確かめない | validate_assignment=True か frozen=True |
model_construct() |
検証も変換もしない | 外から来たデータには使わず model_validate() |
model_copy(update=...) |
update の値を確かめない | model_validate({**m.model_dump(), ...}) で作り直す |
model_copy で id に "not-int" を入れた場合も、そのまま 'not-int' が入りました。外から来た値は、必ず model_validate 系の入口を通します。
よくある質問
Q. Pydanticとは何ですか? A. Python の型ヒントを使って、データを検証し、型をそろえるライブラリです。API の入力・設定・外部の JSON など、外から来るデータの入口で使います(「Pydanticとは」の節)。
Q. Pydanticの読み方は? A. 日本では「パイダンティック」と読まれることが多いです。
Q. Pydanticは標準ライブラリですか?
A. 標準ライブラリではありません。pip install pydantic か uv add pydantic で入れます。2026年10月24日時点の最新は 2.14.1 で、Python 3.10 以上が対象です。
Q. Pydanticとdataclassの違いは?
A. 標準の dataclass は型ヒントを書いても値を確かめず、"x" も入ります。Pydantic は型どおりに変換・検証し、問題を全項目まとめて返します。外から来るデータは Pydantic、検証済みの値を運ぶだけなら dataclass が向いています(違いの節の表)。
Q. PydanticとTypedDictの違いは?
A. TypedDict は dict に型の注釈を付けるだけで、実行時には確かめません。TypeAdapter(TypedDict のクラス) に通すと、Pydantic で検証できます。10万件では BaseModel をまとめて検証するより速く、78.1ms でした。
Q. Pydanticは遅くないですか?
A. 検証のない dataclass より時間はかかります(10万件で 48.0ms と 324.5ms)。リストは TypeAdapter でまとめて、JSON は validate_json に直接渡すと速くなります。v1 より約3.7倍速くなっています。
Q. Pydantic v1のコードはv2でそのまま動きますか?
A. 多くのメソッドは警告つきで動きますが、Optional の必須の決まり、数値→文字列の変換、@validator の書き方などが変わっています。bump-pydantic で書き換えた後、警告を手がかりに対応表で直します。
Q. field_validatorとmodel_validatorはどちらを使えばよいですか?
A. 1項目で完結する決まりは field_validator、2つ以上の項目の関係(開始日と終了日、パスワードの確認など)は model_validator(mode="after") です。
Q. FastAPIではどう使われますか?
A. 引数に BaseModel を書くと、FastAPI が本文の JSON を検証し、合わなければ e.errors() と同じ形の 422 を返します。response_model に書いたモデルの項目だけが応答に出ます。
Q. 検証しないでモデルを作れますか?
A. model_construct() で作れますが、今回の計測では速くなりませんでした。信頼できるデータを、検証なしで作りたいときだけに使います。
Q. mypy などの型チェッカーとPydanticの違いは? A. 型チェッカーはコードを実行する前に、書いたコードの型の食い違いを見つけます。Pydantic は実行中に、外から入ってきた値を確かめます。外からのデータは実行するまで分からないので、両方を使うと役割が分かれます。
まとめ
- Pydantic は、型ヒントどおりにデータを検証し、型をそろえるライブラリ。最新は 2.14.1(2026年10月24日時点、Python 3.10 以上)
- 置き場所は外から来るデータの入口(API・外部の JSON・設定・LLM の出力)。内側の受け渡しは dataclass で済む
- dataclass と TypedDict は値を確かめず、Pydantic は変換・検証して全項目のエラーを返す。attrs は書いた分だけ確かめる
- 既定は lax(
"42"→ 42)。strict=Trueで変換しない。数値→文字列は既定でも変換しない X | Noneは既定値が無ければ必須。項目はfield_validator、項目どうしはmodel_validator(mode="after")- 書き出しは
model_dump(mode="json")/model_dump_json()、読み込みはmodel_validate_json() - 速さは、リストは
TypeAdapterでまとめて、JSON はvalidate_jsonに直接。代入・model_construct・model_copy(update=...)は検証されない
参考・出典
確認日はいずれも 2026年10月24日です。
- Pydantic 公式ドキュメント:https://pydantic.dev/docs/validation/latest/
- Pydantic「Why use Pydantic?」:https://pydantic.dev/docs/validation/latest/get-started/why/
- Pydantic「Models」:https://pydantic.dev/docs/validation/latest/concepts/models/
- Pydantic「Validators」:https://pydantic.dev/docs/validation/latest/concepts/validators/
- Pydantic「Serialization」:https://pydantic.dev/docs/validation/latest/concepts/serialization/
- Pydantic「Strict Mode」:https://pydantic.dev/docs/validation/latest/concepts/strict_mode/
- Pydantic「Dataclasses」:https://pydantic.dev/docs/validation/latest/concepts/dataclasses/
- Pydantic「Settings Management」:https://pydantic.dev/docs/validation/latest/concepts/pydantic_settings/
- Pydantic「Migration Guide」:https://pydantic.dev/docs/validation/latest/get-started/migration/
- PyPI「pydantic」:https://pypi.org/project/pydantic/
- GitHub「pydantic/pydantic」リリース:https://github.com/pydantic/pydantic/releases
- GitHub「pydantic/bump-pydantic」:https://github.com/pydantic/bump-pydantic
- FastAPI「Request Body」:https://fastapi.tiangolo.com/tutorial/body/
- attrs 公式ドキュメント:https://www.attrs.org/
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Pydanticとは|何に使う・dataclassとの違いと使い方を実測で解説」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。