コンテンツにスキップ

Performance tips

ほとんどの場合、Pydanticはボトルネックにはならないので、それが必要だと確信している場合にのみ利用してください。

In general, use model_validate_json() not model_validate(json.loads(...))

model_validate(json.loads(...))では、JSONがPythonで解析され、dictに変換されてから内部で検証されます。 一方、model_validate_json()はすでに内部で検証を実行しています。

model_validate(json.loads(...))の方が高速な場合がいくつかあります。具体的には、モデルに対して'before'または'wrap'バリデータを使用する場合、2ステップ法の方が検証が高速になる可能性があります。 これらの特殊なケースの詳細については、this discussionを参照してください。

現在、pydantic-coreでは、[here]で議論されているように、多くのパフォーマンス改善が行われている(https://github.com/pydantic/pydantic/discussion/6388#discussioncomment-8194048)。 これらの変更がマージされれば、model_validate_json()が常にmodel_validate(json.loads(...))よりも高速になるはずです。

TypeAdapter instantiated once

ここでの考え方は、必要以上にバリデーターとシリアライザーを構築しないようにすることです。 TypeAdapterがインスタンス化されるたびに、新しいバリデータとシリアライザが構築されます。 関数でTypeAdapterを使用している場合、関数が呼び出されるたびにインスタンス化されます。代わりに、一度インスタンス化してから再利用してください。

from typing import List

from pydantic import TypeAdapter


def my_func():
    adapter = TypeAdapter(List[int])
    # do something with adapter
from typing import List

from pydantic import TypeAdapter

adapter = TypeAdapter(List[int])

def my_func():
    ...
    # do something with adapter

Sequence vs list or tuple - Mapping vs dict

Sequenceを使用する場合、Pydanticはisinstance(value, Sequence)を呼び出して、値がシーケンスかどうかをチェックします。 また、Pydanticはlisttupleのような異なるタイプのシーケンスに対して検証を試みます。 値がlistまたはtupleであることがわかっている場合は、Sequenceの代わりにlistまたはtupleを使用してください。

同じことがMappingdictにも当てはまります。 値がdictであることがわかっている場合は、Mappingの代わりにdictを使用してください。

Don't do validation when you don't have to - use Any to keep the value unchanged

値を検証する必要がない場合は、Anyを使用して値を変更しないようにします。

from typing import Any

from pydantic import BaseModel


class Model(BaseModel):
    a: Any


model = Model(a=1)

Avoid extra information via subclasses of primitives

class CompletedStr(str):
    def __init__(self, s: str):
        self.s = s
        self.done = False
from pydantic import BaseModel


class CompletedModel(BaseModel):
    s: str
    done: bool = False

Use tagged union, not union

タグ付きユニオン(または識別されたユニオン)は、それがどのタイプであるかを示すフィールドを持つユニオンです。

from typing import Any

from typing_extensions import Literal

from pydantic import BaseModel, Field


class DivModel(BaseModel):
    el_type: Literal['div'] = 'div'
    class_name: str | None = None
    children: list[Any] | None = None


class SpanModel(BaseModel):
    el_type: Literal['span'] = 'span'
    class_name: str | None = None
    contents: str | None = None


class ButtonModel(BaseModel):
    el_type: Literal['button'] = 'button'
    class_name: str | None = None
    contents: str | None = None


class InputModel(BaseModel):
    el_type: Literal['input'] = 'input'
    class_name: str | None = None
    value: str | None = None


class Html(BaseModel):
    contents: DivModel | SpanModel | ButtonModel | InputModel = Field(
        discriminator='el_type'
    )

詳細については、Discriminated Unionsを参照してください。

Use TypedDict over nested models

ネストされたモデルを使用する代わりに、TypedDictを使用してデータの構造を定義してください。

Performance comparison

単純なベンチマークでは、TypedDictはネストされたモデルよりも約2.5倍高速です。

from timeit import timeit

from typing_extensions import TypedDict

from pydantic import BaseModel, TypeAdapter


class A(TypedDict):
    a: str
    b: int


class TypedModel(TypedDict):
    a: A


class B(BaseModel):
    a: str
    b: int


class Model(BaseModel):
    b: B


ta = TypeAdapter(TypedModel)
result1 = timeit(
    lambda: ta.validate_python({'a': {'a': 'a', 'b': 2}}), number=10000
)
result2 = timeit(
    lambda: Model.model_validate({'b': {'a': 'a', 'b': 2}}), number=10000
)
print(result2 / result1)

Avoid wrap validators if you really care about performance

一般に、ラップ・バリデーターは他のバリデーターよりも低速です。 これは、検証時にPythonでデータを実体化する必要があるためです。ラップ・バリデーターは複雑な検証ロジックには非常に便利ですが、最高のパフォーマンスを求めるのであれば、ラップ・バリデーターは避けるべきです。

Failing early with FailFast

v2.8以降では、シーケンス内のいずれかの項目が検証に失敗した場合に早期に失敗するように、シーケンスタイプにFailFastアノテーションを適用できるようになりました。 このアノテーションを使用すると、シーケンス内の残りの項目が失敗しても検証エラーが発生しないため、可視性とパフォーマンスを効果的にトレードオフすることができます。

from typing import List

from typing_extensions import Annotated

from pydantic import FailFast, TypeAdapter, ValidationError

ta = TypeAdapter(Annotated[List[bool], FailFast()])
try:
    ta.validate_python([True, 'invalid', False, 'also invalid'])
except ValidationError as exc:
    print(exc)
    """
    1 validation error for list[bool]
    1
      Input should be a valid boolean, unable to interpret input [type=bool_parsing, input_value='invalid', input_type=str]
    """

FailFastについての詳細はこちらhereをご覧ください。