---
title: "Pydantic v2のConfigDict設定一覧｜model_configの書き方とv1からの移行【2.14対応】"
url: "https://www.issoh.co.jp/tech/details/7583/"
published: 2025-07-07
updated: 2026-10-21
categories: ["Python"]
publisher: "株式会社一創"
---

# Pydantic v2のConfigDict設定一覧｜model\_configの書き方とv1からの移行【2.14対応】

Pydantic v2でモデルの挙動を変えるときは、クラス属性 `model_config` に `ConfigDict` を代入します。ConfigDictは設定項目を型付きで並べた辞書（TypedDict）で、未定義のキーを拒否する、型変換を止める、インスタンスを変更不可にする、といった指定をモデル単位でまとめて行えます。この記事では2026年10月11日公開のPydantic 2.14.1をPython 3.12で実際に動かし、主要オプションの既定値と違反時のエラー、2.14で変わった点、v1の `class Config` からの移行手順を出力つきで整理します。

## まとめ：ConfigDictで押さえる要点

コード例は本文の順に1つのPythonファイルとして実行しており、前の例のimportやモデル定義を後の例が使います。

- 書き方は `model_config = ConfigDict(...)`、通常の辞書、`class M(BaseModel, extra="forbid")` のクラス引数の3通りで、結果は同じ。`model_config` 側でキーのつづりを間違えても実行時は何も起きず、mypyなどの型チェッカだけが検出する（クラス引数で間違えた場合は定義時に `TypeError` になる）。
- 主要オプションの既定値は `extra='ignore'`、`strict=False`、`frozen=False`、`validate_assignment=False`、`from_attributes=False`、`validate_by_alias=True`、`validate_by_name=False`、`serialize_by_alias=False`。
- 設定はモデルごとに閉じている。親モデルに `extra="forbid"` を付けても、フィールドとして持つ子モデルの余分なキーは黙って捨てられる。
- 2.14から `model_config` は書いたとおりの内容を保つ。`populate_by_name=True` を書いても、2.13までのように `validate_by_name` などが自動で書き足されない。
- `populate_by_name` は公式が「v2.11以降は推奨せず、v3で非推奨にする」と明記。新規コードは `validate_by_name=True` と `validate_by_alias=True` で書く。
- 2.14はPython 3.9を対象外にした。3.9の環境で `pip install pydantic` を実行すると、入るのは3.9に対応する2.13系まで。

Pydanticそのものの位置づけや読み方から確認したい場合は、[Pydanticとは？読み方・BaseModelの使い方とV1との違い【2.13対応】](/tech/details/4838/)で基礎を押さえてから戻ってくると読みやすくなります。

## model\_configとConfigDictの基本形

### ConfigDict・辞書・クラス引数の3つの書き方

公式の[Configurationの解説](https://pydantic.dev/docs/validation/latest/concepts/config/)は、モデルへの設定方法として `model_config` 属性とクラス引数の2つを挙げ、`model_config` には通常の辞書も使えると書いています。3つとも同じ設定になることを確認します。

```
from pydantic import BaseModel, ConfigDict

class A1(BaseModel):
    model_config = ConfigDict(extra="forbid")
    a: int

class A2(BaseModel):
    model_config = {"extra": "forbid"}
    a: int

class A3(BaseModel, extra="forbid"):
    a: int

print(A1.model_config == A2.model_config == A3.model_config)  # True
```

差が出るのはつづりを間違えたときです。`model_config` 側で `extra` を `extr` と書いても、2.14.1は例外も警告も出さずにモデルを作り、余分な入力は既定の `ignore` で捨てられます。クラス引数で `class M(BaseModel, extr="forbid")` と書いた場合は、逆にクラス定義の時点で `TypeError` になります。

```
class Typo(BaseModel):
    model_config = ConfigDict(extr="forbid")  # extraのつづり誤り
    a: int

print(Typo(a=1, b=2))  # a=1（例外も警告も出ない）
```

同じ内容をmypy 2.4.0にかけると `error: Extra key "extr" for TypedDict "ConfigDict" [typeddict-unknown-key]` が出ました。`model_config` はConfigDict型として宣言されているため、辞書で書いた場合も同じエラーになります。`model_config` の設定ミスを拾えるのは型チェッカだけなので、ConfigDictを使うプロジェクトではCIで型チェックを回すのが前提になります。

### class Configが出す非推奨警告と削除予定

v1では設定を内部クラス `Config` に書きました。v2でも動作しますが、クラス定義の時点で `PydanticDeprecatedSince20` が発生します。

```
import warnings

with warnings.catch_warnings(record=True) as w:
    warnings.simplefilter("always")

    class M(BaseModel):
        class Config:
            extra = "forbid"

        a: int

    for x in w:
        print(type(x.message).__name__, "|", x.message)

print("model_config ->", M.model_config)

# PydanticDeprecatedSince20 | Support for class-based `config` is deprecated, use ConfigDict
#   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/
# model_config -> {'extra': 'forbid'}
```

警告文のとおり削除予定はV3.0です。`class Config` の中身は `model_config` に変換されて効いているので、移行は同じキーと値をConfigDictへ移すだけで済みます。ただしv1から名前が変わったキーがあり、公式の移行ガイドの一覧では `orm_mode` は `from_attributes`、`validate_all` は `validate_default`、`anystr_strip_whitespace` は `str_strip_whitespace`、`allow_population_by_field_name` は `populate_by_name`（2.11以降は `validate_by_name`）に置き換えます。

### 継承でのマージと多重継承の優先順位

サブクラスに書いた `model_config` は親の設定とマージされ、同じキーだけが上書きされます。

```
class Base(BaseModel):
    model_config = ConfigDict(extra="forbid", str_strip_whitespace=True)

class Child(Base):
    model_config = ConfigDict(extra="allow")  # extraだけ上書き

print(Child.model_config)
# {'extra': 'allow', 'str_strip_whitespace': True}
```

プロジェクト共通の設定を1つの基底クラスにまとめ、全モデルにそれを継承させる書き方は、公式も設定を全体に効かせる方法として案内しています。注意が要るのは多重継承です。公式ドキュメントは「複数の親を持つ場合、Pydanticは現時点ではMROに従わない」と注記しており、2.14.1で確かめると、後ろに書いた親の設定が勝ちました。

```
class Forbid(BaseModel):
    model_config = ConfigDict(extra="forbid")

class Allow(BaseModel):
    model_config = ConfigDict(extra="allow")

class M1(Forbid, Allow):
    x: int

class M2(Allow, Forbid):
    x: int

print(M1.model_config, M2.model_config)
# {'extra': 'allow'} {'extra': 'forbid'}
```

Pythonのメソッド解決順序なら `M1` は先に書いた `Forbid` が優先されるはずなので、直感と逆の結果です。設定を持つ親を2つ以上継承する設計は避け、共通設定は単一の基底クラスに集めてください。

### ネストした子モデルには設定が伝わらない

親モデルの設定は、フィールドとして持つ子モデルには適用されません。公式はこれを「各モデルが自分の設定の境界を持つ」と説明しています。

```
class Item(BaseModel):
    name: str

class Order(BaseModel):
    model_config = ConfigDict(extra="forbid")
    item: Item

print(Order(item={"name": "a", "color": "red"}))
# item=Item(name='a')  子モデルの余分なキー color は黙って捨てられる
```

APIの入力を厳密にしたくて最上位のモデルにだけ `extra="forbid"` を付けると、ネストした部分の未知キーやタイプミスは素通りします。入力スキーマ全体を締めるなら、前の節の共通基底クラスに `extra="forbid"` を書き、子モデルもそれを継承させるのが確実です。なお標準ライブラリのdataclassやTypedDictをフィールドに使った場合は、それ自身に設定が無ければ親の設定が伝わります。

## ConfigDict主要オプションの既定値と違反時のエラー

よく使うオプションの既定値と、違反時に返るエラー種別（`e.errors()[0]["type"]`）を2.14.1で確認した結果です。既定値は[公式のConfigDictリファレンス](https://pydantic.dev/docs/validation/latest/api/pydantic/config/)の記載と一致しました（`strict` と `from_attributes` はリファレンスに既定値の明記が無く、実行時の挙動で確認）。

| オプション                | 既定値      | 変えたときの効果                | 違反時のtype         |
| -------------------- | -------- | ----------------------- | ---------------- |
| extra                | ‘ignore’ | 未定義キーを拒否／保持             | extra\_forbidden |
| strict               | False    | 文字列の数字などの型変換を止める        | int\_type など     |
| frozen               | False    | 代入を禁止し\_\_hash\_\_を生成する | frozen\_instance |
| validate\_assignment | False    | 代入時にも検証する               | int\_parsing など  |
| from\_attributes     | False    | オブジェクトの属性から読む           | model\_type      |
| validate\_default    | False    | 既定値も検証する                | int\_parsing など  |
| validate\_by\_alias  | True     | Falseで別名での入力を拒否する       | missing          |
| validate\_by\_name   | False    | フィールド名での入力を受け付ける        | missing          |
| serialize\_by\_alias | False    | 出力のキーを別名にする             | なし               |

なお、何も設定していないモデルの `model_config` は空の辞書です。既定値は辞書に書き込まれず、Pydantic内部で補われます。

### extraの3つの挙動（ignore・forbid・allow）

既定の `ignore` では、定義していないキーを渡してもエラーにならず、値は捨てられます。`forbid` にすると `extra_forbidden` で拒否します。

```
from pydantic import Field, ValidationError

class U(BaseModel):
    model_config = ConfigDict(extra="forbid")
    name: str

try:
    U(name="a", age=3)
except ValidationError as e:
    print(e.errors())

# [{'type': 'extra_forbidden', 'loc': ('age',), 'msg': 'Extra inputs are not permitted',
#   'input': 3, 'url': 'https://errors.pydantic.dev/2.14/v/extra_forbidden'}]
```

`allow` は余分なキーを捨てずに保持し、`model_extra` で取り出せます。`model_dump()` の出力にも含まれます。

```
class E(BaseModel):
    model_config = ConfigDict(extra="allow")
    a: int

e = E(a=1, b="x")
print(e.model_extra)   # {'b': 'x'}
print(e.model_dump())  # {'a': 1, 'b': 'x'}
```

外部から受け取るリクエストボディのように、クライアントのタイプミスを早く検出したい入力には `forbid` を使います。外部APIのレスポンスを読む側は、相手がフィールドを追加しただけで処理が落ちないよう、既定の `ignore` のままにします。`allow` は中身の分からない追加データを素通しで転送したい場合に限られます。

### strictモードとlaxモードの型変換の差

既定のlaxモードは、文字列の `"5"` を整数の5へ変換して受け入れます。`strict=True` にすると変換せずにエラーを返します。

```
class L(BaseModel):
    n: int

class S(BaseModel):
    model_config = ConfigDict(strict=True)
    n: int

print(L(n="5").n, type(L(n="5").n))  # 5 <class 'int'>

try:
    S(n="5")
except ValidationError as e:
    print(e.errors()[0]["type"], "|", e.errors()[0]["msg"])
# int_type | Input should be a valid integer
```

strictはモデル全体に効くため、フォームやクエリ文字列のように値が文字列で届く入力に付けると正当な値まで弾きます。JSON本文の型をそのまま守らせたい内部APIに向いた設定です。

### frozenとvalidate\_assignmentの使い分け

`frozen=True` は生成後の代入を禁止し、`validate_assignment=True` は代入を許したうえで値を検証し直します。既定ではどちらも無効で、代入時に検証は走りません。

```
class F(BaseModel):
    model_config = ConfigDict(frozen=True)
    x: int

try:
    F(x=1).x = 2
except ValidationError as e:
    print(e.errors()[0]["type"])  # frozen_instance

class VA(BaseModel):
    model_config = ConfigDict(validate_assignment=True)
    x: int

va = VA(x=1)
try:
    va.x = "abc"
except ValidationError as e:
    print(e.errors()[0]["type"])  # int_parsing

class NoVA(BaseModel):
    x: int

n = NoVA(x=1)
n.x = "abc"
print(repr(n.x))  # 'abc'
```

最後の例のとおり、既定の設定では `int` と宣言したフィールドに文字列が入ります。生成後に値を書き換えるモデルは `validate_assignment=True` を付けるか、書き換えないなら `frozen=True` にして代入そのものを禁止してください。

### from\_attributesでORMオブジェクトからモデルを作る

SQLAlchemyのモデルのように属性で値を持つオブジェクトは、既定では `model_validate` に渡せません。v1の `orm_mode` に当たるのが `from_attributes` です。

```
class Row:
    def __init__(self):
        self.id = 1
        self.name = "alice"

class UserOut(BaseModel):
    id: int
    name: str

try:
    UserOut.model_validate(Row())
except ValidationError as e:
    print(e.errors()[0]["type"])  # model_type

print(UserOut.model_validate(Row(), from_attributes=True))  # id=1 name='alice'

class UserORM(BaseModel):
    model_config = ConfigDict(from_attributes=True)
    id: int
    name: str

print(UserORM.model_validate(Row()))  # id=1 name='alice'
```

呼び出しごとに `from_attributes=True` を渡す方法と、`model_config` に書く方法の結果は同じです。ORMの行を返すレスポンス用モデルは設定に書き、まれにしか使わない変換は呼び出し側で指定すると、意図がコードに残ります。コンストラクタ `UserORM(Row())` は位置引数を受け付けず `TypeError` になるので、変換は必ず `model_validate` を通します。ORMとモデルを1つのクラスにまとめる方法は、[SQLModelのモデル定義とテーブル作成｜unique制約・CRUD・リレーション・FastAPI連携【0.0.42】](/tech/details/10322/)で扱っています。

### 文字列・Enum・既定値の扱いを変えるオプション

入力の前処理に使うオプションです。`str_strip_whitespace` と `str_to_lower` は文字列の前後の空白除去と小文字化、`use_enum_values` はEnumの代わりに値を保持、`coerce_numbers_to_str` は数値から文字列への変換を許可します。

```
import enum

class Color(enum.Enum):
    RED = "red"

class Opt(BaseModel):
    model_config = ConfigDict(
        str_strip_whitespace=True,
        str_to_lower=True,
        use_enum_values=True,
        coerce_numbers_to_str=True,
    )
    name: str
    color: Color
    code: str

o = Opt(name="  ALICE ", color="red", code=123)
print(repr(o.name), repr(o.color), repr(o.code))  # 'alice' 'red' '123'

class Def(BaseModel):
    n: int = "x"  # 型と合わない既定値

class DefV(BaseModel):
    model_config = ConfigDict(validate_default=True)
    n: int = "x"

print(repr(Def().n))  # 'x'（既定値は検証されない）
try:
    DefV()
except ValidationError as e:
    print(e.errors()[0]["type"])  # int_parsing
```

`coerce_numbers_to_str` が無いと、`str` 型のフィールドに123を渡した時点で `string_type` エラーになります。商品コードや郵便番号が数値でも文字列でも届くような入力で使う設定です。`use_enum_values` には落とし穴があり、公式リファレンスは「Enum型のフィールドに既定値がある場合、既定値にも効かせるには `validate_default` が必要」と注記しています。既定値は検証を通らないため、`Def` の例のように型と合わない値もそのまま残ります。

## エイリアスの設定：populate\_by\_nameからvalidate\_by\_nameへ

### populate\_by\_nameの現状と2.14でのmodel\_configの変化

`populate_by_name=True` は、別名（alias）とフィールド名のどちらでも入力を受け付ける設定です。2.14.1でも警告なしに動きます。

```
class A(BaseModel):
    model_config = ConfigDict(populate_by_name=True)
    user_id: int = Field(alias="userId")

print(A(userId=1), A(user_id=2))  # user_id=1 user_id=2
print(A.model_config)             # {'populate_by_name': True}
```

2.13.5で同じコードを実行すると、最後の行は `{'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}` でした。[2.14のリリース記事](https://pydantic.dev/articles/pydantic-v2-14-release)は「model\_configはPydanticに書き換えられなくなり、常に利用者が書いた内容（親クラスの設定とのマージ結果）を表す」と説明しています。`Model.model_config["validate_by_name"]` のように展開後のキーを読んでいたコードは、2.14で `KeyError` になります。入力の受け付け方そのものは変わっていません。

公式リファレンスは `populate_by_name` について「v2.11以降は使用を推奨せず、v3で非推奨にする」と明記し、代わりに `validate_by_name` を使うよう案内しています。`validate_by_name=True` と `validate_by_alias=True` の組み合わせが、`populate_by_name=True` と厳密に同じ動作です。

### validate\_by\_nameとvalidate\_by\_aliasの組み合わせ

2.11で入った2つのオプションは独立に切り替えられます。`validate_by_alias=False` にすると、別名での入力は受け付けません。

```
import inspect

class C(BaseModel):
    model_config = ConfigDict(validate_by_name=True, validate_by_alias=False)
    user_id: int = Field(alias="userId")

print(C(user_id=3))  # user_id=3

try:
    C(userId=3)
except ValidationError as e:
    print(e.errors()[0]["type"], e.errors()[0]["loc"])  # missing ('user_id',)

print(inspect.signature(C))  # (*, user_id: int) -> None
```

最後の行は2.14の変更点です。2.13までは `validate_by_alias=False` でもシグネチャに別名の `userId` が出ており、`help()` やIPythonの補完など実行時にシグネチャを読むツールの表示と、実際に受け付ける引数が食い違っていました。2.14ではフィールド名で表示されます。なお、2つとも `False` にするとどちらの名前でも入力できなくなるため、モデル定義の時点でエラーになります。

### alias\_generatorとto\_camelでcamelCaseのJSONを扱う

フロントエンドとcamelCaseのJSONでやり取りしつつ、Python側はsnake\_caseで書きたい場合は、フィールドごとに `alias` を書かずに `alias_generator` で一括変換します。

```
from pydantic.alias_generators import to_camel

class Api(BaseModel):
    model_config = ConfigDict(
        alias_generator=to_camel,
        validate_by_name=True,
        validate_by_alias=True,
        serialize_by_alias=True,
    )
    user_id: int
    created_at: str

a = Api.model_validate({"userId": 1, "createdAt": "2026-10-21"})
print(a)  # user_id=1 created_at='2026-10-21'
print(Api(user_id=2, created_at="x").model_dump())
# {'userId': 2, 'createdAt': 'x'}
```

`serialize_by_alias=True` を書かないと、`model_dump()` はsnake\_caseのキーで出力し、毎回 `by_alias=True` を渡す必要があります。公式リファレンスによると、このオプションは2.11で追加され、v3では既定値が `True` に変わる見込みです。いまのうちに明示しておけば、v3へ上げたときに出力のキーが変わる事故を防げます。別名を持ちながらsnake\_caseで出力しているモデルは、逆に `serialize_by_alias=False` を明示しておきます。

## 入力の検証とValidationErrorの扱い

### model\_validateとコンストラクタ、model\_validate\_jsonの使い分け

コンストラクタはキーワード引数を、`model_validate` は辞書などのオブジェクトを1つ受け取ります。外部から届いたJSON文字列は、`json.loads` で辞書にせず `model_validate_json` へ直接渡します。公式の[Performanceの解説](https://pydantic.dev/docs/validation/latest/concepts/performance/)も、最初の項目で `model_validate(json.loads(...))` ではなく `model_validate_json()` を使うよう勧めています。

```
class P(BaseModel):
    user_id: int = Field(alias="userId")

print(P.model_validate({"userId": 8}))          # user_id=8
print(P.model_validate_json('{"userId": 9}'))   # user_id=9
```

`model_validate` には `strict` や `from_attributes`、`context` を呼び出しごとに渡せます。モデルの設定は変えずに、特定の経路だけ厳しくしたいときに使います。

### ValidationError.errors()の構造とエラー種別での分岐

`errors()` はエラーごとに `loc`（どのフィールドか）、`type`（機械可読な種別）、`msg`（人向けの文）を持つ辞書のリストを返します。

```
try:
    U(name="a", age=3)
except ValidationError as e:
    for er in e.errors():
        print(er["loc"], er["type"], er["msg"])
# ('age',) extra_forbidden Extra inputs are not permitted
```

画面に出すメッセージを切り替えるときは、`msg` の文面ではなく `type` で分岐してください。文面はバージョンで変わりえますが、`type` は公式の[エラーの解説](https://pydantic.dev/docs/validation/latest/errors/errors/)で「機械可読な識別子」と定義されています。

### model\_constructで検証を飛ばしてよい場面

`model_construct` は検証を行わずにインスタンスを作ります。型が合わない値もそのまま入ります。

```
bad = P.model_construct(user_id="not-an-int")
print(repr(bad.user_id), type(bad.user_id))  # 'not-an-int' <class 'str'>
```

使ってよいのは、自分のデータベースから読み出した直後など、検証済みだと保証できるデータを大量に組み立てる場面だけです。外部入力に使うと、`extra="forbid"` の拒否や `strict`、型変換など、ここまで見た検証の設定はどれも効きません。

## model\_dumpとmodel\_dump\_jsonによる出力制御

`model_dump()` はPythonのオブジェクトのまま辞書にし、`mode="json"` を付けるとJSONに出せる型（文字列など）へ変換します。`model_dump_json()` はJSON文字列を直接返します。

```
import datetime

class D(BaseModel):
    at: datetime.datetime

d = D(at=datetime.datetime(2026, 10, 21, 12, 0))
print(d.model_dump())             # {'at': datetime.datetime(2026, 10, 21, 12, 0)}
print(d.model_dump(mode="json"))  # {'at': '2026-10-21T12:00:00'}

p = P.model_validate({"userId": 7})
print(p.model_dump())               # {'user_id': 7}
print(p.model_dump(by_alias=True))  # {'userId': 7}
```

v1の `dict()` と `json()` はそれぞれ `model_dump()` と `model_dump_json()` に置き換わりました。別名で出力するかどうかを毎回の引数で決めるか、前章の `serialize_by_alias` でモデルに固定するかは、そのモデルの出力先が1つかどうかで決めます。

## フィールド制約とバリデータの書き方

### conint・constrからAnnotatedへの移行と制約の比較

`conint` や `constr` はv2でも動きますが、公式は `Annotated` と `Field` の組み合わせを推奨し、`conint` などはPydantic 3.0で非推奨にすると予告しています。生成されるJSON Schemaは同じです。

```
from typing import Annotated
from pydantic import conint, constr

class Old(BaseModel):
    age: conint(ge=0, le=120)
    code: constr(min_length=2, max_length=4)

class New(BaseModel):
    age: Annotated[int, Field(ge=0, le=120)]
    code: Annotated[str, Field(min_length=2, max_length=4)]

print(Old.model_json_schema()["properties"]["age"]
      == New.model_json_schema()["properties"]["age"])  # True
print(New.model_json_schema()["properties"]["age"])
# {'maximum': 120, 'minimum': 0, 'title': 'Age', 'type': 'integer'}
```

`conint(...)` は型ヒントの位置で関数を呼ぶ書き方なので、mypyなどの型チェッカは中身を `int` として扱えません。`Annotated` なら型は `int` のままで、制約だけがメタデータとして付きます。

### field\_validatorとmodel\_validatorの役割分担

1つのフィールドの値を確かめるのが `field_validator`、複数フィールドの関係を確かめたり補ったりするのが `model_validator` です。v1の `validator` と `root_validator` に当たります。

```
from pydantic import field_validator, model_validator

class Order2(BaseModel):
    qty: int
    unit_price: int
    total: int | None = None

    @field_validator("qty")
    @classmethod
    def positive(cls, v: int) -> int:
        if v <= 0:
            raise ValueError("qty must be positive")
        return v

    @model_validator(mode="after")
    def fill_total(self):
        if self.total is None:
            self.total = self.qty * self.unit_price
        return self

print(Order2(qty=3, unit_price=100))
# qty=3 unit_price=100 total=300
```

`mode="after"` のバリデータは型変換が済んだインスタンスを受け取るので、計算は整数同士で行えます。変換前の生の入力を見たいときだけ `mode="before"` を使います。

### Optional（int | None）のNone許容とフィールド省略の条件

v2では「Noneを入れられる」ことと「省略できる」ことが別の条件になりました。`Optional[int]` と同じ意味の `int | None` を付けても、既定値を書かなければ必須のままです。

```
class O(BaseModel):
    a: int | None         # 必須。Noneは入れられるが省略はできない
    b: int | None = None  # 省略可能

try:
    O(b=1)
except ValidationError as e:
    print(e.errors()[0]["loc"], e.errors()[0]["type"])  # ('a',) missing
```

v1では `Optional` を付けると暗黙に既定値Noneが入ったため、v1のコードをそのまま移すと必須のフィールドが増えます。公式の移行ガイドも「Required, optional, and nullable fields」の節でこの違いを扱っています。なお、可変な既定値はインスタンスごとに複製されるので、リストを既定値に書いても共有されません。

```
class Tags(BaseModel):
    tags: list = []

t1, t2 = Tags(), Tags()
t1.tags.append("a")
print(t1.tags, t2.tags)  # ['a'] []
```

## pydantic-settingsのSettingsConfigDictとextraの既定値

環境変数や `.env` を読む設定クラスは、v2で別パッケージの `pydantic-settings` に分かれました。設定は `SettingsConfigDict` に書きます。ここで見落としやすいのが、`BaseSettings` の `extra` の既定値が `'forbid'` である点です。

```
from pydantic_settings import BaseSettings, SettingsConfigDict

print(BaseSettings.model_config["extra"])  # forbid

class AppSettings(BaseSettings):
    model_config = SettingsConfigDict(env_prefix="APP_", env_file=".env")
    port: int = 8000

# .env の中身: APP_PORT=9000 と DEBUG=true の2行
# 環境変数 APP_UNKNOWN=1 を設定して実行
try:
    print(AppSettings())
except ValidationError as e:
    print(e.errors()[0]["type"], e.errors()[0]["loc"])
# extra_forbidden ('debug',)
```

pydantic-settings 2.16.1で実行すると、`.env` にある対応フィールドの無い `DEBUG` が `extra_forbidden` で拒否されました。一方、同じく対応フィールドの無い環境変数 `APP_UNKNOWN` は接頭辞が一致していても無視され、エラーになりません。[公式のSettings Management](https://pydantic.dev/docs/validation/latest/concepts/pydantic%5Fsettings/)も、extraは渡された入力を検証するもので `os.environ` の全キーは見ないと説明しています。つまり環境変数名のつづり誤りは検出されず、既定値のまま動きます。`.env` を他のツールと共有していて無関係な行がある場合は、`SettingsConfigDict` に `dotenv_filtering="match_prefix"` を書くと、`extra` は `forbid` のまま接頭辞 `APP_` の付いた行だけを読ませられます（上の例は `port=9000` で起動）。`extra="ignore"` にする方法もありますが、その場合は `.env` のつづり誤りも黙って捨てられます。

## v1からv2への移行で最初に直す箇所

移行で最初に手を入れるのは、`class Config` を `model_config = ConfigDict(...)` に書き換える箇所と、`parse_obj`・`dict()`・`json()` を `model_validate`・`model_dump()`・`model_dump_json()` に置き換える箇所です。いずれもPydanticの非推奨警告（`PydanticDeprecatedSince20`）が出るので、pytestを `-W error::pydantic.warnings.PydanticDeprecatedSince20` 付きで回すと、他のライブラリの警告は通したまま残りを失敗として洗い出せます。`-W error::DeprecationWarning` にすると、無関係なライブラリの警告まで失敗になります。手順の全体は公式の[移行ガイド](https://pydantic.dev/docs/validation/latest/get-started/migration/)にまとまっています。

### 2.14でPython 3.9が対象外になった影響

2.14.0（2026年10月8日公開）はPython 3.9のサポートを終了し、3.15に対応しました。PyPIの登録情報では、2.14.1の `requires_python` は `>=3.10`、2.13.5は `>=3.9` です。3.9の環境ではpipが3.9に対応する最後の2.13系を選ぶため、エラーにならずに古い版が入ります。2.14の変更（前述の `model_config` の扱いなど）を前提にしたコードは、3.9のCIでは別の版で動いていることになるので、CIのPythonの版を先に3.10以上へ上げてください。3.10以上なら、この記事の例のように `Optional[int]` を `int | None` と書けます（`typing.List` を `list` と書く形は3.9から使えます）。

### 移行ツールとpydantic.v1名前空間の現状

公式の移行ツールbump-pydanticは、PyPIの最終版が0.8.0（2023年12月28日）で、[GitHubのリポジトリ](https://github.com/pydantic/bump-pydantic)は2026年5月27日にアーカイブされました。変換できるのは定型的な書き換えに限られるので、使う場合も差分を読み、残りは非推奨警告とテストで直します。

v2パッケージには旧APIを使える `pydantic.v1` 名前空間が同梱されています。

```
import pydantic, pydantic.v1 as v1

print(pydantic.VERSION, v1.VERSION)  # 2.14.1 1.10.26
```

モジュール単位で `import pydantic.v1 as v1` に読み替えれば、残りを後回しにして段階的に移せます。ただし[V1の公式ドキュメント](https://pydantic.dev/docs/validation/1.10/overview/)は「最後にサポートするPythonは3.13で、V1はPython 3.14以降では動かない」と明記しています。1.10.25で3.14への最小限の対応が入り、手元の3.14.6でも単純なモデルと `class Config` の検証は動きましたが、サポート対象外であることに変わりはありません。Python 3.14へ上げる予定があるなら、`pydantic.v1` に残したコードの移行を先に終えてください。

### GenericModelからBaseModelとGenericへの移行

v1の `pydantic.generics.GenericModel` は、v2では `BaseModel` と `typing.Generic` の多重継承に置き換わりました。

```
from typing import Generic, TypeVar

T = TypeVar("T")

class Page(BaseModel, Generic[T]):
    items: list[T]
    total: int

class User(BaseModel):
    id: int

print(Page[User].model_validate({"items": [{"id": 1}], "total": 1}))
# items=[User(id=1)] total=1
```

ページング付きのAPIレスポンスのように、包む側の形が共通で中身の型だけが変わるモデルに使います。FastAPIのレスポンスモデルとして使う全体像は[FastAPIとは｜最短で動かす手順とasync/defの使い分け【2026年版】](/tech/details/3859/)で扱っています。

## V1とV2の処理速度：同一環境での実測比較

V2の設計を公表した[公式記事（2022年7月10日）](https://pydantic.dev/articles/pydantic-v2)は、pydantic-coreのベンチマークで「V1.9.1より4倍から50倍速く、一般的なフィールドを含むモデルの検証ではおよそ17倍速い」と書いています。倍率はモデルの形で大きく変わるため、2.14.1と同梱のpydantic.v1 1.10.26を同一プロセスで計測しました。同梱のV1は `compiled` が `False` のPython実装で、単体配布のCythonコンパイル版との比較ではありません。

- 環境：macOS（Intel Core i9-9880H）、Python 3.12.13、pydantic 2.14.1、pydantic-core 2.50.1、pydantic.v1 1.10.26
- 対象：`id`・`name`・`price`・`tags` を持つ子モデルを10件ネストした注文モデル
- 計測：`timeit.repeat` でN=20,000回を3回繰り返した最小値。スクリプトを2回実行し、表は1回目の値（比の括弧内は2回目）

```
import timeit, json
import pydantic
import pydantic.v1 as v1

class C2(pydantic.BaseModel):
    id: int; name: str; price: float; tags: list[str]

class O2(pydantic.BaseModel):
    order_id: int; items: list[C2]

class C1(v1.BaseModel):
    id: int; name: str; price: float; tags: list[str]

class O1(v1.BaseModel):
    order_id: int; items: list[C1]

data = {"order_id": 1, "items": [{"id": i, "name": f"item{i}", "price": "9.5",
                                  "tags": ["a", "b"]} for i in range(10)]}
js = json.dumps(data)
o2, o1 = O2.model_validate(data), O1.parse_obj(data)
N = 20000

def t(f):
    return min(timeit.repeat(f, number=N, repeat=3)) / N * 1e6  # 1回あたりのマイクロ秒

print(t(lambda: O2.model_validate(data)), t(lambda: O1.parse_obj(data)))
print(t(lambda: O2.model_validate_json(js)), t(lambda: O1.parse_raw(js)))
print(t(lambda: o2.model_dump_json()), t(lambda: o1.json()))
```

| 処理         | V2     | V1      | 比            |
| ---------- | ------ | ------- | ------------ |
| 辞書の検証      | 40.3us | 312.2us | 7.7倍（8.3倍）   |
| JSON文字列の検証 | 49.5us | 357.3us | 7.2倍（7.4倍）   |
| JSON出力     | 24.3us | 358.2us | 14.7倍（12.8倍） |

検証は7倍から8倍、JSON出力は13倍から15倍でした。2026年9月にPython 3.9.6と2.13.5で同じ形のモデルを測ったときは辞書の検証が12.9倍でした。そのときは計測方法（timeitの合計をNで割った値）もPythonとPydanticの版も違うので単純には比べられませんが、V2の時間は41.0usとほぼ同じで、V1の時間が528.7usから312.2usに縮んでいます。倍率は環境で大きく動くということです。

速度だけを理由に移行を計画するなら、本番相当のモデルで検証時間と件数を先に測るべきです。測らなくても移行を正当化できる理由は別にあり、`class Config` や旧メソッドがV3.0で削除される予定であること、`pydantic.v1` がPython 3.14以降をサポートしないこと、ConfigDictの設定ミスを型チェッカで拾えることの3点です。

APIの入出力設計からPydanticのモデル定義まで外部に任せたい場合は、[API開発・システム連携の支援](/service/system/api/)で相談を受けています。LLMの出力をPydanticのモデルで検証する使い方は、[LLM構造化出力とは？JSON Schemaで出力形式を保証する仕組みと実装・見送り条件を解説【2026年版】](/tech/details/16322/)で扱っています。

## よくある質問

### Pydantic v2でclass Configはまだ使えますか？

2.14.1でも動きますが、クラス定義の時点で `PydanticDeprecatedSince20` の警告が出て、V3.0で削除予定と明記されています。中身は `model_config` に変換されて効いているので、同じキーと値を `model_config = ConfigDict(...)` に移せば警告は消えます。`orm_mode` など名前の変わったキーだけは新しい名前に直してください。

### `model_config = ConfigDict(extra="forbid")` は何をする設定ですか？

モデルに定義していないキーが入力に含まれていたら、`extra_forbidden` のValidationErrorで拒否する設定です。既定の `extra="ignore"` では余分なキーが黙って捨てられます。ただし効くのはそのモデル自身だけで、フィールドとして持つ子モデルには伝わらないため、子モデルにも同じ設定が必要です。

### populate\_by\_name=True は今も使えますか？

2.14.1でも警告なしに動き、別名とフィールド名の両方で入力を受け付けます。ただし公式は「v2.11以降は推奨せず、v3で非推奨にする」と明記しているため、新規のコードは `validate_by_name=True` と `validate_by_alias=True` を書いておくと、v3で非推奨になったときに書き換えずに済みます。2.14からは `model_config` に展開後のキーが書き足されなくなった点にも注意してください。

### model\_configという名前のフィールドは定義できますか？

定義できません。2.14.1で `model_config: str` というフィールドを書くと、クラス定義の時点で `PydanticUserError`（model\_config cannot be used as a model field name）になります。外部のJSONに同じ名前のキーがある場合は、Pythonのフィールド名を別にして `Field(alias="model_config")` で受け取ります。

### v1のコードを残したまま移行できますか？

できます。v2パッケージには `pydantic.v1` 名前空間が同梱されており、2.14.1には1.10.26が入っています。`import pydantic.v1 as v1` に読み替えれば旧APIのまま動くので、モジュール単位で順に移せます。ただしV1はPython 3.14以降を公式にサポートしていないため、恒久的な置き場所にはせず、Pythonを上げる前に移行を終えてください。

## 関連記事

- [Pydanticとは？読み方・BaseModelの使い方とV1との違い【2.13対応】](/tech/details/4838/)
- [FastAPIとは｜最短で動かす手順とasync/defの使い分け【2026年版】](/tech/details/3859/)
- [SQLModelのモデル定義とテーブル作成｜unique制約・CRUD・リレーション・FastAPI連携【0.0.42】](/tech/details/10322/)
- [LLM構造化出力とは？JSON Schemaで出力形式を保証する仕組みと実装・見送り条件を解説【2026年版】](/tech/details/16322/)
- [PydanticAIとは｜型安全なPythonエージェントの作り方を実装コードで解説](/tech/details/6743/)

---

出典: [Pydantic v2のConfigDict設定一覧｜model\_configの書き方とv1からの移行【2.14対応】](<https://www.issoh.co.jp/tech/details/7583/>)（株式会社一創）
