性能提示¶
在大多数情况下,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
Sequence
与 list
或 tuple
,以及 Mapping
与 dict
的对比¶
当使用 Sequence
时,Pydantic 会调用 isinstance(value, Sequence)
来检查值是否为序列。此外,Pydantic 会尝试对不同类型的序列(如 list
和 tuple
)进行验证。如果你知道值的类型是 list
或 tuple
,就应该直接使用 list
或 tuple
而不是 Sequence
。
这同样适用于 Mapping
和 dict
。如果你知道值的类型是 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
的信息:链接。