pydantic

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Pydantic 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
Field()
function is used to provide metadata and constraints. You need to distinguish two types of of metadata:
  • field specific metadata: metadata such as
    deprecated
    ,
    alias
    , that only has a meaning when attached to a field.
  • type specific metadata: this includes constraints such as
    gt
    ,
    max_length
    , and also metadata that affects the JSON Schema (e.g.
    description
    ,
    title
    ).
The
Field()
function can be attached to model fields using the assignment form:
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)] = 1
The annotated pattern has some advantages:
  • Using the
    f: <type> = Field(...)
    form can be confusing and might trick users into thinking
    f
    has a default value, while in reality it is still required.
  • You can provide an arbitrary amount of metadata elements for a field. As shown in the example above. the
    Field()
    function only supports a limited set of constraints/metadata, and you may have to use different Pydantic utilities such as
    WithJsonSchema
    in some cases.
But note that:
  • You should use the assignment form for metadata that has a meaning for static type checkers. This includes:
    alias
    ,
    default
    and
    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)] = None
    field specific metadata should apply to the whole union in this example.
Field()
函数用于提供元数据和约束。你需要区分两种类型的元数据:
  • 字段特定元数据:诸如
    deprecated
    alias
    这类仅在附加到字段时才有意义的元数据。
  • 类型特定元数据:包括
    gt
    max_length
    等约束,以及影响JSON Schema的元数据(例如
    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()
    函数仅支持有限的约束/元数据集,某些情况下你可能需要使用
    WithJsonSchema
    等其他Pydantic工具。
但请注意:
  • 对于对静态类型检查器有意义的元数据(包括
    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
Field()
function. For example, string constraints such as
strip_whitespace
,
to_upper
,
to_lower
and
ascii_only
can only be specified using
pydantic.StringConstraints
:
python
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_whitespace
to_upper
to_lower
ascii_only
等字符串约束只能通过
pydantic.StringConstraints
指定:
python
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 value
Using 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
int
, strings like
'123'
will be accepted. This also applies to collections types:
list[str]
also accepts tuples, sets etc.
This is way you should avoid:
  • using unions such as
    int | str
    , if your goal is to coerce the
    str
    to an
    int
    via a validator.
  • using abstract collections such as
    collections.abc.Sequence
    , if your goal is to accept both list and tuples. Using these abstract collections is inefficient.
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.
除非使用严格模式,否则Pydantic在大多数情况下会应用类型转换。例如,对于类型为
int
的字段,
'123'
这样的字符串会被接受。这也适用于集合类型:
list[str]
也接受元组、集合等。
因此你应该避免:
  • 使用
    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
from __future__ import annotations
if possible (which stringifies all annotations by default). Only add explicit quotes to annotations that aren't defined yet, e.g.:
python
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 annotations
(它会默认将所有注解转换为字符串)。仅对尚未定义的注解添加显式引号,例如:
python
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 | None

Or, 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')
undefined
from typing_extensions import TypeAliasType
JsonValue = TypeAliasType('JsonValue', 'list[JsonValue] | dict[str, JsonValue] | str | bool | int | float | None')
undefined

Model 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
m
:
python
m.model_dump()
#> {'model': {'base_field': 1}} -> sub1_field missing
This is because Pydantic serializes the model according to the defined type (
Base
), not the runtime value. Validation will also be unexpected if doing
Main(model={'base_field': 1, 'sub1_field': 'test'})
.
Instead, try to use discriminated unions (provided that you can set a
type
field to distinguish models):
python
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
or generics:
python
class Main[BaseT: Base](BaseModel):
    model: BaseT

m = Main[Sub1](model={'base_field': 1, 'sub1_field': 'test'})  # Will work
using 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'))
这个示例可以运行,但序列化
m
时的行为不符合预期:
python
m.model_dump()
#> {'model': {'base_field': 1}} -> sub1_field缺失
这是因为Pydantic会根据定义的类型(
Base
)而非运行时的值来序列化模型。如果执行
Main(model={'base_field': 1, 'sub1_field': 'test'})
,验证结果也会不符合预期。
相反,尝试使用可区分联合类型(前提是你可以设置一个
type
字段来区分模型):
python
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)。