性能优化技巧¶
在大多数情况下,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])
# 使用 adapter 进行某些操作
from pydantic import TypeAdapter
adapter = TypeAdapter(list[int])
def my_func():
...
# 使用 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),而不是普通联合¶
标记联合(或可区分联合)是一种带有指示类型的字段的联合类型。
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)
如果非常关心性能,请避免使用包装验证器¶
包装验证器通常比其他验证器慢。这是因为它们要求 在验证过程中数据必须在 Python 中具体化。包装验证器对于复杂的验证逻辑非常有用, 但如果你追求最佳性能,应该避免使用它们。
使用 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 的信息请参阅此处。