🐍

【Python】Pydantic, TypedDict で予約語をキーに使いたいときの対処法

に公開

Python には TypedDict というものがあり、これを使うと辞書型のオブジェクトの型を静的にチェックしてくれるようになります。TypeScript の type に似ています。

from typing import TypedDict

class Person(TypedDict):
    name: str
    age: int

person: Person = {
    "name": "Alice",
    "age": 30,
}

person["age"] = "30"  # <- Type Error

最近は Pydantic のおかげで TypedDict の出番が少なくなっていますが、既存のコードが辞書型を使っていて、型チェックが効くようにしたい場合は TypedDict の方が低コストで行えます。

ただし、Python の予約語をキーにしたい場合、例えば from と to というキーを持つ辞書型の型を定義したい場合、うまくいきません。

TypedDict 予約語キー NG
from typing import TypedDict

class Range(TypedDict):
    from: int  # <- from が予約語なのでエラーになる
    to: int

Pydantic でもこの問題は起こります。

Pydantic 予約語キー NG
from pydantic import BaseModel

class Range(BaseModel):
    from: int  # <- from が予約語なのでエラーになる
    to: int

例えば API のスキーマ定義が from になっている場合などに困ることがあるかもしれません。

これらの問題の解決方法を紹介します。

TypedDict の場合

TypedDict は以下のように dict を渡して定義することもできます。

予約語キー OK
Range = TypedDict(
    "Range",
    {
        "from": int,
        "to": int,
    },
)

これで一応解決できます。

ただし、ジェネリック型を使おうとすると、型パラメーターが渡せないのでこれではうまくいきません。(方法はあるのかもしれませんが、知りません。)

型パラメーター NG
from typing import Generic, TypedDict, TypeVar

T = TypeVar("T")

Range = TypedDict(  # <- 型パラメーターが渡せない
    "Range",
    {
        "from": int,
        "to": int,
        "something": T,
    },
)

class SomeData(TypedDict, Generic[T]):
    name: str
    range: Range[T]

そういう場合は以下のようにすると良いです。

型パラメーター OK
from typing import Any, Generic, TypedDict, TypeVar

T = TypeVar("T")

_Range = TypedDict(
    "_Range",
    {
        "from": int,
        "to": int,
    },
)

class Range(_Range, Generic[T]):
    something: T

class SomeData(TypedDict, Generic[T]):
    name: str
    range: Range[T]

from をジェネリック型にしたい場合はどうすればいいんでしょうね。

Pydantic の場合

困るのは API 等の実装の時ぐらいだとも思うので、Pydantic 内では一貫して from_を使い、dict では一貫して from を使う、という方針でいきます。

以下のように実現できます。

pydantic
from pydantic import BaseModel, ConfigDict, Field

class Range(BaseModel):
    model_config = ConfigDict(populate_by_name=True)

    from_: int = Field(
        ...,
        serialization_alias="from",  # <- model_dump の出力のキーを from にする
        validation_alias="from",  # <- model_validate の入力のキーを from にする
    )
    to: int


_range = Range(
    from_=1,  # Field で alias="from" とするとここでエラー
    to=10,
)
range = Range.model_validate({"from": 1, "to": 10})
print(range.from_)  # <- 1
print(range.model_dump(by_alias=True))  # <- {'from': 1, 'to': 10}
GitHubで編集を提案

Discussion