跳转到内容

性能提示

在大多数情况下,Pydantic 不会成为你的性能瓶颈,只有在你确定有必要时才遵循这些建议。

通常,使用 model_validate_json() 而不是 model_validate(json.loads(...))

使用 model_validate(json.loads(...)) 时,JSON 会先在 Python 中被解析,然后转换为一个字典,最后在内部进行验证。而 model_validate_json() 则直接在内部执行验证。

在少数情况下,model_validate(json.loads(...)) 可能会更快。具体来说,当在模型上使用 'before''wrap' 验证器时,采用这种两步法验证可能会更快。你可以在这个讨论中阅读更多关于这些特殊情况的信息。

目前,pydantic-core 正在进行许多性能改进,详见此讨论。一旦这些更改被合并,model_validate_json() 应该总是比 model_validate(json.loads(...)) 更快。

TypeAdapter 只实例化一次

这里的思路是避免不必要地构造验证器和序列化器。每次实例化 TypeAdapter 时,都会构造一个新的验证器和序列化器。如果你在一个函数中使用 TypeAdapter,那么每次调用该函数时它都会被实例化。正确的做法是只实例化一次,然后复用它。

from pydantic import TypeAdapter


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

adapter = TypeAdapter(list[int])

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

Sequencelisttuple,以及 Mappingdict 的对比

当使用 Sequence 时,Pydantic 会调用 isinstance(value, Sequence) 来检查值是否为序列。此外,Pydantic 会尝试对不同类型的序列(如 listtuple)进行验证。如果你知道值的类型是 listtuple,就应该直接使用 listtuple 而不是 Sequence

这同样适用于 Mappingdict。如果你知道值的类型是 dict,就应该使用 dict 而不是 Mapping

非必要不验证,使用 Any 保持值不变

如果你不需要验证某个值,请使用 Any 来保持该值不变。

from typing import Any

from pydantic import BaseModel


class Model(BaseModel):
    a: Any


model = Model(a=1)

避免通过原始类型的子类来传递额外信息

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

使用带标签的联合体 (tagged union),而不是联合体 (union)

带标签的联合体(或可辨识联合体)是一种带有一个字段来指明其具体类型的联合体。

from typing import Any, 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'
    )

更多详情请参见可辨识联合体

使用 TypedDict 而不是嵌套模型

使用 TypedDict 来定义数据结构,而不是使用嵌套模型。

性能比较

通过一个简单的基准测试,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)

如果真的关心性能,请避免使用 wrap 验证器

Wrap 验证器通常比其他验证器慢。这是因为它们需要在验证过程中将数据在 Python 中实例化。对于复杂的验证逻辑,Wrap 验证器可能非常有用,但如果你追求最佳性能,则应避免使用它们。

使用 FailFast 尽早失败

从 v2.8+ 版本开始,你可以将 FailFast 注解应用于序列类型,以便在序列中任何一项验证失败时立即终止。如果使用此注解,当某一项失败时,你将不会收到序列中其余项的验证错误,因此这实际上是在用可见性换取性能。

from typing 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 的信息:链接