pydantic
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePydantic Validation
Pydantic验证
In a nutshell, Pydantic is dataclasses with runtime validation. It leverages type hints
to understand how validation (and serialization) should be performed. It is mostly useful
when dealing with external untrusted data, for example when defining an HTTP API.
It is generally not recommended to use Pydantic to define classes that are instantiated within the user code.
By doing so, you will lose flexibility (e.g. can't use types not supported by Pydantic, harder to perform
post init changes). It is usually better to use vanilla classes (or standard library dataclasses) in this case,
as a static type checker will already catch type mismatches.
简而言之,Pydantic是具备运行时验证功能的数据类(dataclasses)。它利用类型提示来确定应如何执行验证(以及序列化)操作。在处理外部不可信数据时(例如定义HTTP API时),它尤为实用。
通常不建议使用Pydantic来定义在用户代码内部实例化的类。这样做会损失灵活性(例如无法使用Pydantic不支持的类型,难以在初始化后进行修改)。这种情况下,通常更好的选择是使用普通类(或标准库的dataclasses),因为静态类型检查器已经可以捕获类型不匹配问题。
Basic usage
基础用法
Here is a simple example of using a Pydantic model:
python
from datetime import date
from pydantic import BaseModel, Field
class Person(BaseModel):
name: str
age: int = Field(description='The age of the person')
birthdate: date | None = None
p = Person(name='John', age=20, birthdate='1970-01-01')以下是使用Pydantic模型的简单示例:
python
from datetime import date
from pydantic import BaseModel, Field
class Person(BaseModel):
name: str
age: int = Field(description='The age of the person')
birthdate: date | None = None
p = Person(name='John', age=20, birthdate='1970-01-01')Constraints and field metadata
约束与字段元数据
The function is used to provide metadata and constraints.
You need to distinguish two types of of metadata:
Field()- field specific metadata: metadata such as ,
deprecated, that only has a meaning when attached to a field.alias - type specific metadata: this includes constraints such as ,
gt, and also metadata that affects the JSON Schema (e.g.max_length,description).title
The function can be attached to model fields using the assignment form:
Field()python
class User(BaseModel):
first_name: str = Field(alias='name')or using the annotated pattern:
python
class Model(BaseModel):
value: Annotated[int, Field(deprecated=True)] = 1The annotated pattern has some advantages:
- Using the form can be confusing and might trick users into thinking
f: <type> = Field(...)has a default value, while in reality it is still required.f - You can provide an arbitrary amount of metadata elements for a field. As shown in the example above.
the function only supports a limited set of constraints/metadata, and you may have to use different Pydantic utilities such as
Field()in some cases.WithJsonSchema
But note that:
-
You should use the assignment form for metadata that has a meaning for static type checkers. This includes:,
aliasanddefault.default_factory -
field specific metadata can only be used on the "top-level" type. A common pitfall is to do the following:python
class Model(BaseModel): field_bad: Annotated[int, Field(deprecated=True)] | None = None field_ok: Annotated[int | None, Field(deprecated=True)] = Nonefield specific metadata should apply to the whole union in this example.
Field()- 字段特定元数据:诸如、
deprecated这类仅在附加到字段时才有意义的元数据。alias - 类型特定元数据:包括、
gt等约束,以及影响JSON Schema的元数据(例如max_length、description)。title
Field()python
class User(BaseModel):
first_name: str = Field(alias='name')或者使用注解模式:
python
class Model(BaseModel):
value: Annotated[int, Field(deprecated=True)] = 1注解模式有一些优势:
- 使用的形式可能会造成混淆,让用户误以为
f: <type> = Field(...)有默认值,但实际上它仍然是必填项。f - 你可以为字段提供任意数量的元数据元素。如上述示例所示,函数仅支持有限的约束/元数据集,某些情况下你可能需要使用
Field()等其他Pydantic工具。WithJsonSchema
但请注意:
-
对于对静态类型检查器有意义的元数据(包括、
alias和default),应使用赋值形式。default_factory -
字段特定元数据只能用于"顶层"类型。一个常见的错误示例如下:python
class Model(BaseModel): field_bad: Annotated[int, Field(deprecated=True)] | None = None field_ok: Annotated[int | None, Field(deprecated=True)] = None在这个示例中,字段特定元数据应应用于整个联合类型。
Constraints
约束
As much as possible, use the "built-in" validation constraints, instead of defining
custom validators:
python
from annotated_types import Gt # annotated_types is an alternative to the `Field()` function.
class Model(BaseModel):
constrained_int_ok: Annotated[int, Gt(1)] # This is good
constrained_int_bad: int
@field_validator('constrained_int_bad') # This is bad
@classmethod
def validate(cls, v: int):
if not v > 1:
raise ValueError('Value is not greater than 1')Sometimes, constraints can't be expressed using the function. For example, string constraints such
as , , and can only be specified using :
Field()strip_whitespaceto_upperto_lowerascii_onlypydantic.StringConstraintspython
from typing import Annotated
from pydantic import BaseModel, StringConstraints
class Model(BaseModel):
# Do this instead of a validator calling s.strip():
a: Annotated[str, StringConstraints(strip_whitespace=True)]https://pydantic.dev/docs/validation/latest/api/pydantic/standard_library_types/ is the canonical documentation for all
supported standard library types and their constraints.
尽可能使用"内置"验证约束,而非定义自定义验证器:
python
from annotated_types import Gt # annotated_types是`Field()`函数的替代方案。
class Model(BaseModel):
constrained_int_ok: Annotated[int, Gt(1)] # 推荐写法
constrained_int_bad: int
@field_validator('constrained_int_bad') # 不推荐写法
@classmethod
def validate(cls, v: int):
if not v > 1:
raise ValueError('Value is not greater than 1')
return v有时,约束无法通过函数表达。例如,、、和等字符串约束只能通过指定:
Field()strip_whitespaceto_upperto_lowerascii_onlypydantic.StringConstraintspython
from typing import Annotated
from pydantic import BaseModel, StringConstraints
class Model(BaseModel):
# 推荐这种写法,而非使用调用s.strip()的验证器:
a: Annotated[str, StringConstraints(strip_whitespace=True)]https://pydantic.dev/docs/validation/latest/api/pydantic/standard_library_types/ 是所有受支持标准库类型及其约束的官方文档。
Validators
验证器
In some cases, you may have to use custom validators. As much as possible, use after validators. Because they run after
the Pydantic validation, you are guaranteed to work with the type of the field being validated. If you use before validators,
the input data can literally be anything, so it is more error-prone (especially for model validators, the input isn't
necessarily a dict, it can also be an arbitrary object).
If possible, prefer using the annotated pattern for validators:
python
from pydantic import BaseModel, ValidationError, field_validator
def is_even(value: int) -> int:
if value % 2 == 1:
raise ValueError(f'{value} is not an even number')
return value
class Model(BaseModel):
# Prefer this form: the validator is right next to the field, making it easy to understand
even: Annotated[int, AfterValidator(is_even)]
odd: int
# If you define a validator as decorator, make sure to define it as classmethod.
@field_validator('odd', mode='after')
@classmethod
def is_odd(cls, value: int) -> int:
if value % 2 == 0:
raise ValueError(f'{value} is not an odd number')
return valueUsing the decorator pattern can lead to unclear behavior, especially when considering the order in which they run
(in particular when using subclasses).
某些情况下,你可能不得不使用自定义验证器。尽可能使用after验证器,因为它们在Pydantic验证之后运行,你可以确保处理的是待验证字段的正确类型。如果使用before验证器,输入数据可能是任意类型,因此更容易出错(尤其是模型验证器,输入不一定是字典,也可能是任意对象)。
如果可能,优先为验证器使用注解模式:
python
from pydantic import BaseModel, ValidationError, field_validator
def is_even(value: int) -> int:
if value % 2 == 1:
raise ValueError(f'{value} is not an even number')
return value
class Model(BaseModel):
# 优先选择这种形式:验证器紧邻字段,便于理解
even: Annotated[int, AfterValidator(is_even)]
odd: int
# 如果将验证器定义为装饰器,请确保将其定义为类方法。
@field_validator('odd', mode='after')
@classmethod
def is_odd(cls, value: int) -> int:
if value % 2 == 0:
raise ValueError(f'{value} is not an odd number')
return value使用装饰器模式可能会导致行为不明确,尤其是在考虑它们的运行顺序时(特别是使用子类时)。
Type coercion, collections and unions
类型转换、集合与联合类型
Unless you are using strict mode, Pydantic applies
type coercion in most cases. For instance, for a field typed as , strings like will be accepted. This also
applies to collections types: also accepts tuples, sets etc.
int'123'list[str]This is way you should avoid:
- using unions such as , if your goal is to coerce the
int | strto anstrvia a validator.int - using abstract collections such as , if your goal is to accept both list and tuples. Using these abstract collections is inefficient.
collections.abc.Sequence
In the general case, unions are best avoided because every use of the field will need to check for each type before
doing anything with it.
因此你应该避免:
- 使用这类联合类型,如果你的目标是通过验证器将
int | str转换为str。int - 使用这类抽象集合类型,如果你的目标是同时接受列表和元组。使用这些抽象集合类型效率较低。
collections.abc.Sequence
一般情况下,应尽量避免使用联合类型,因为每次使用字段时都需要检查每种类型才能进行操作。
Forward annotations
前向注解
Python has the ability to write annotations as forward references, by using strings. This can cause challenges for Pydantic
to evaluate them, so they are best avoided if possible.
If you are defining Pydantic models in a module, avoid using if possible
(which stringifies all annotations by default). Only add explicit quotes to annotations that aren't defined yet, e.g.:
from __future__ import annotationspython
class Model(BaseModel):
self_ref: 'Model'Also note that in Python >= 3.14, annotations evaluation is deferred, so you should not use string annotations at all.
Python支持通过字符串编写前向引用注解,这可能会给Pydantic解析它们带来挑战,因此应尽可能避免使用。
如果在模块中定义Pydantic模型,请尽可能避免使用(它会默认将所有注解转换为字符串)。仅对尚未定义的注解添加显式引号,例如:
from __future__ import annotationspython
class Model(BaseModel):
self_ref: 'Model'另外需要注意,在Python >= 3.14中,注解的解析会被延迟,因此你完全不应该使用字符串注解。
Recursive type aliases
递归类型别名
You might be tempted to define aliases like this:
python
JsonValue: TypeAlias = 'list[JsonValue] | dict[str, JsonValue] | str | bool | int | float | None'The alias needs to be quoted because it is a recursive one. Pydantic will generally not be able to evaluate the alias.
Instead, use an explicit type alias:
python
type JsonValue = list[JsonValue] | dict[str, JsonValue] | str | bool | int | float | None你可能会尝试定义如下类型别名:
python
JsonValue: TypeAlias = 'list[JsonValue] | dict[str, JsonValue] | str | bool | int | float | None'由于这是递归别名,需要加引号。但Pydantic通常无法解析该别名。相反,应使用显式类型别名:
python
type JsonValue = list[JsonValue] | dict[str, JsonValue] | str | bool | int | float | NoneOr, if not on Python >= 3.12:
或者,如果使用的Python版本 <3.12:
from typing_extensions import TypeAliasType
JsonValue = TypeAliasType('JsonValue', 'list[JsonValue] | dict[str, JsonValue] | str | bool | int | float | None')
undefinedfrom typing_extensions import TypeAliasType
JsonValue = TypeAliasType('JsonValue', 'list[JsonValue] | dict[str, JsonValue] | str | bool | int | float | None')
undefinedModel subclasses, discriminated unions
模型子类、可区分联合类型
Subclassing is a really common Python pattern, but can be a footgun in Pydantic. You might be tempted to do:
python
class Base(BaseModel):
base_field: int
def common_method(self): ...
class Sub1(Base):
sub1_field: str
class Sub2(Base):
sub2_field: bool
class Main(BaseModel):
model: Base
m = Main(model=Sub1(base_field=1, sub1_field='test'))This example works, but will not behave as expected when serializing :
mpython
m.model_dump()
#> {'model': {'base_field': 1}} -> sub1_field missingThis is because Pydantic serializes the model according to the defined type (), not the runtime value.
Validation will also be unexpected if doing .
BaseMain(model={'base_field': 1, 'sub1_field': 'test'})Instead, try to use discriminated unions (provided that you can set a field to distinguish models):
typepython
class Sub1(Base):
type: Literal['sub1']
sub1_field: str
class Sub2(Base):
type: Literal['sub2']
sub2_field: bool
Subs = Annotated[Sub1 | Sub2, Field(discriminator='type')]
class Main(BaseModel):
model: Subsor generics:
python
class Main[BaseT: Base](BaseModel):
model: BaseT
m = Main[Sub1](model={'base_field': 1, 'sub1_field': 'test'}) # Will workusing polymorphic serialization (in Pydantic >=2.13)
or serialize as any (in Pydantic <2.13)
can be used as last resort.
子类化是非常常见的Python模式,但在Pydantic中可能会导致问题。你可能会尝试这样做:
python
class Base(BaseModel):
base_field: int
def common_method(self): ...
class Sub1(Base):
sub1_field: str
class Sub2(Base):
sub2_field: bool
class Main(BaseModel):
model: Base
m = Main(model=Sub1(base_field=1, sub1_field='test'))这个示例可以运行,但序列化时的行为不符合预期:
mpython
m.model_dump()
#> {'model': {'base_field': 1}} -> sub1_field缺失这是因为Pydantic会根据定义的类型()而非运行时的值来序列化模型。如果执行,验证结果也会不符合预期。
BaseMain(model={'base_field': 1, 'sub1_field': 'test'})相反,尝试使用可区分联合类型(前提是你可以设置一个字段来区分模型):
typepython
class Sub1(Base):
type: Literal['sub1']
sub1_field: str
class Sub2(Base):
type: Literal['sub2']
sub2_field: bool
Subs = Annotated[Sub1 | Sub2, Field(discriminator='type')]
class Main(BaseModel):
model: Subs或者使用泛型:
python
class Main[BaseT: Base](BaseModel):
model: BaseT
m = Main[Sub1](model={'base_field': 1, 'sub1_field': 'test'}) # 可以正常工作作为最后手段,可以使用多态序列化(适用于Pydantic >=2.13)或serialize as any(适用于Pydantic <2.13)。