跳转到内容

字段

API 文档

pydantic.fields.Field

在本节中,我们将介绍可用的机制,用于自定义 Pydantic 模型字段:默认值JSON Schema 元数据约束等。

为此,大量使用了 Field() 函数,其行为与标准库中用于数据类的 field() 函数相同 - 通过赋值给标注属性

from pydantic import BaseModel, Field


class Model(BaseModel):
    name: str = Field(frozen=True)

注意

尽管 `name` 被赋予了一个值,但它仍然是必需的,并且没有默认值。如果你想强调必须提供一个值,你可以使用省略号

class Model(BaseModel):
    name: str = Field(..., frozen=True)

然而,不鼓励使用它,因为它与静态类型检查器配合不佳。

标注模式

为了对模型字段应用约束或附加 Field() 函数,Pydantic 还支持 Annotated 类型构造来将元数据附加到注解上

from typing import Annotated

from pydantic import BaseModel, Field, WithJsonSchema


class Model(BaseModel):
    name: Annotated[str, Field(strict=True), WithJsonSchema({'extra': 'data'})]

就静态类型检查器而言,`name` 仍然被类型化为 `str`,但 Pydantic 利用可用的元数据来添加验证逻辑、类型约束等。

使用此模式有一些优点

  • 使用 `f: = Field(...)` 形式可能会令人困惑,并可能误导用户认为 `f` 有默认值,而实际上它仍然是必需的。
  • 您可以为字段提供任意数量的元数据元素。如上例所示,Field() 函数仅支持有限的约束/元数据集,在某些情况下您可能需要使用不同的 Pydantic 实用程序,例如 WithJsonSchema
  • 类型可以重用(参见自定义类型文档,使用此模式)。

然而,请注意,Field() 函数的某些参数(即 `default`、`default_factory` 和 `alias`)会被静态类型检查器考虑,以合成正确的 `__init__()` 方法。它们不理解标注模式,因此您应该改用普通赋值形式。

提示

标注模式还可以用于向类型的特定部分添加元数据。例如,可以这样添加验证约束

from typing import Annotated

from pydantic import BaseModel, Field


class Model(BaseModel):
    int_list: list[Annotated[int, Field(gt=0)]]
    # Valid: [1, 3]
    # Invalid: [-1, 2]

注意不要混淆*字段*和*类型*元数据

class Model(BaseModel):
    field_bad: Annotated[int, Field(deprecated=True)] | None = None  # (1)!
    field_ok: Annotated[int | None, Field(deprecated=True)] = None  # (2)!
  1. Field() 函数应用于 `int` 类型,因此 `deprecated` 标志不会有任何作用。尽管考虑到 Field() 函数的名称会暗示它应该应用于字段,但这可能会令人困惑,因为在设计该 API 时,此函数是提供元数据的唯一方式。您可以选择使用 Pydantic 现在支持的 annotated_types 库。

  2. Field() 函数应用于“顶层”联合类型,因此 `deprecated` 标志将应用于该字段。

默认值

字段的默认值可以通过正常赋值语法或通过向 `default` 参数提供值来提供

from pydantic import BaseModel, Field


class User(BaseModel):
    # Both fields aren't required:
    name: str = 'John Doe'
    age: int = Field(default=20)

警告

在 Pydantic V1 中,即使没有明确指定默认值,被标注为 Any 或被 Optional 包装的类型也会被赋予隐式的默认值 `None`。在 Pydantic V2 中不再是这种情况。

您还可以将一个可调用对象传递给 `default_factory` 参数,该参数将被调用以生成默认值

from uuid import uuid4

from pydantic import BaseModel, Field


class User(BaseModel):
    id: str = Field(default_factory=lambda: uuid4().hex)

默认工厂还可以接受一个必需的参数,在这种情况下,已验证的数据将作为字典传递。

from pydantic import BaseModel, EmailStr, Field


class User(BaseModel):
    email: EmailStr
    username: str = Field(default_factory=lambda data: data['email'])


user = User(email='[email protected]')
print(user.username)
#> [email protected]

`data` 参数将*只*包含已验证的数据,基于模型字段的顺序(如果 `username` 在 `email` 之前定义,则上述示例将失败)。

验证默认值

默认情况下,Pydantic 将*不*验证默认值。`validate_default` 字段参数(或 validate_default 配置值)可用于启用此行为

from pydantic import BaseModel, Field, ValidationError


class User(BaseModel):
    age: int = Field(default='twelve', validate_default=True)


try:
    user = User()
except ValidationError as e:
    print(e)
    """
    1 validation error for User
    age
      Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='twelve', input_type=str]
    """

可变默认值

Python 中常见的 bug 来源是使用可变对象作为函数或方法参数的默认值,因为每次调用都会重用相同的实例。

dataclasses 模块在这种情况下会引发错误,表明您应该改用默认工厂

虽然在 Pydantic 中也可以做同样的事情,但它不是必需的。如果默认值不可哈希,Pydantic 将在创建模型的每个实例时创建默认值的深拷贝

from pydantic import BaseModel


class Model(BaseModel):
    item_counts: list[dict[str, int]] = [{}]


m1 = Model()
m1.item_counts[0]['a'] = 1
print(m1.item_counts)
#> [{'a': 1}]

m2 = Model()
print(m2.item_counts)
#> [{}]

字段别名

提示

专用部分中阅读有关别名的更多信息。

对于验证和序列化,您可以为字段定义一个别名。

有三种定义别名的方法

  • Field(alias='foo')
  • Field(validation_alias='foo')
  • Field(serialization_alias='foo')

`alias` 参数用于验证和序列化。如果您想分别为验证和序列化使用*不同的*别名,可以使用 `validation_alias` 和 `serialization_alias` 参数,它们将仅在其各自的用例中应用。

以下是使用 `alias` 参数的示例

from pydantic import BaseModel, Field


class User(BaseModel):
    name: str = Field(alias='username')


user = User(username='johndoe')  # (1)!
print(user)
#> name='johndoe'
print(user.model_dump(by_alias=True))  # (2)!
#> {'username': 'johndoe'}
  1. 别名 `'username'` 用于实例创建和验证。
  2. 我们正在使用 model_dump() 将模型转换为可序列化格式。

    请注意,`by_alias` 关键字参数默认为 `False`,必须明确指定才能使用字段(序列化)别名转储模型。

    您还可以使用 ConfigDict.serialize_by_alias 在模型级别配置此行为。

    当 `by_alias=True` 时,在序列化期间使用别名 `'username'`。

如果您只想为*验证*使用别名,可以使用 `validation_alias` 参数

from pydantic import BaseModel, Field


class User(BaseModel):
    name: str = Field(validation_alias='username')


user = User(username='johndoe')  # (1)!
print(user)
#> name='johndoe'
print(user.model_dump(by_alias=True))  # (2)!
#> {'name': 'johndoe'}
  1. 验证别名 `'username'` 在验证期间使用。
  2. 字段名称 `'name'` 在序列化期间使用。

如果您只想为*序列化*定义一个别名,可以使用 `serialization_alias` 参数

from pydantic import BaseModel, Field


class User(BaseModel):
    name: str = Field(serialization_alias='username')


user = User(name='johndoe')  # (1)!
print(user)
#> name='johndoe'
print(user.model_dump(by_alias=True))  # (2)!
#> {'username': 'johndoe'}
  1. 字段名称 `'name'` 用于验证。
  2. 序列化别名 `'username'` 用于序列化。

别名优先级

如果您同时使用 `alias` 和 `validation_alias` 或 `serialization_alias`,`validation_alias` 在验证时将优先于 `alias`,而 `serialization_alias` 在序列化时将优先于 `alias`。

如果您为 alias_generator 模型设置提供了一个值,您可以通过 `alias_priority` 字段参数控制字段别名和生成别名的优先级顺序。您可以在此处阅读有关别名优先级的更多信息。

静态类型检查/IDE 支持

如果您为 `alias` 字段参数提供一个值,静态类型检查器将使用此别名而不是实际字段名称来合成 `__init__` 方法

from pydantic import BaseModel, Field


class User(BaseModel):
    name: str = Field(alias='username')


user = User(username='johndoe')  # (1)!
  1. 被类型检查器接受。

这意味着当使用 validate_by_name 模型设置(允许在模型验证期间同时使用字段名称和别名)时,如果使用实际字段名称,类型检查器将报错

from pydantic import BaseModel, ConfigDict, Field


class User(BaseModel):
    model_config = ConfigDict(validate_by_name=True)

    name: str = Field(alias='username')


user = User(name='johndoe')  # (1)!
  1. *不*被类型检查器接受。

如果你仍然希望类型检查器使用字段名称而不是别名,可以使用标注模式(仅 Pydantic 理解)

from typing import Annotated

from pydantic import BaseModel, ConfigDict, Field


class User(BaseModel):
    model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)

    name: Annotated[str, Field(alias='username')]


user = User(name='johndoe')  # (1)!
user = User(username='johndoe')  # (2)!
  1. 被类型检查器接受。
  2. *不*被类型检查器接受。

验证别名

尽管 Pydantic 在创建模型实例时对 `alias` 和 `validation_alias` 的处理方式相同,但类型检查器只理解 `alias` 字段参数。作为一种变通方法,您可以同时指定 `alias` 和 `serialization_alias`(与字段名称相同),因为 `serialization_alias` 在序列化期间会覆盖 `alias`

from pydantic import BaseModel, Field


class MyModel(BaseModel):
    my_field: int = Field(validation_alias='myValidationAlias')

from pydantic import BaseModel, Field


class MyModel(BaseModel):
    my_field: int = Field(
        alias='myValidationAlias',
        serialization_alias='my_field',
    )


m = MyModel(myValidationAlias=1)
print(m.model_dump(by_alias=True))
#> {'my_field': 1}

字段约束

Field() 函数也可以用来为特定类型添加约束

from decimal import Decimal

from pydantic import BaseModel, Field


class Model(BaseModel):
    positive: int = Field(gt=0)
    short_str: str = Field(max_length=3)
    precise_decimal: Decimal = Field(max_digits=5, decimal_places=2)

每种类型可用的约束(以及它们如何影响 JSON Schema)在标准库类型文档中描述。

严格字段

Field() 函数的 `strict` 参数指定字段是否应在严格模式下进行验证。

from pydantic import BaseModel, Field


class User(BaseModel):
    name: str = Field(strict=True)
    age: int = Field(strict=False)  # (1)!


user = User(name='John', age='42')  # (2)!
print(user)
#> name='John' age=42
  1. 这是默认值。
  2. `age` 字段以宽松模式验证。因此,它可以被赋值一个字符串。

标准库类型文档描述了每种类型的严格行为。

数据类字段

Field() 函数的一些参数可用于数据类

  • `init`:该字段是否应包含在数据类的合成 `__init__()` 方法中。
  • `init_var`:该字段是否应为数据类中的仅初始化字段。
  • `kw_only`:该字段是否应为数据类构造函数中的仅关键字参数。

这是一个例子

from pydantic import BaseModel, Field
from pydantic.dataclasses import dataclass


@dataclass
class Foo:
    bar: str
    baz: str = Field(init_var=True)
    qux: str = Field(kw_only=True)


class Model(BaseModel):
    foo: Foo


model = Model(foo=Foo('bar', baz='baz', qux='qux'))
print(model.model_dump())  # (1)!
#> {'foo': {'bar': 'bar', 'qux': 'qux'}}
  1. 由于 `baz` 字段是仅初始化字段,因此它不包含在序列化输出中。

字段表示

参数 `repr` 可用于控制字段是否应包含在模型的字符串表示中。

from pydantic import BaseModel, Field


class User(BaseModel):
    name: str = Field(repr=True)  # (1)!
    age: int = Field(repr=False)


user = User(name='John', age=42)
print(user)
#> name='John'
  1. 这是默认值。

判别式

参数 `discriminator` 可用于控制将用于区分联合中不同模型的字段。它接受字段名称或 `Discriminator` 实例。当判别字段在 `Union` 中的所有模型中不相同时,`Discriminator` 方法可能很有用。

以下示例展示了如何将 `discriminator` 与字段名称一起使用

from typing import Literal, Union

from pydantic import BaseModel, Field


class Cat(BaseModel):
    pet_type: Literal['cat']
    age: int


class Dog(BaseModel):
    pet_type: Literal['dog']
    age: int


class Model(BaseModel):
    pet: Union[Cat, Dog] = Field(discriminator='pet_type')


print(Model.model_validate({'pet': {'pet_type': 'cat', 'age': 12}}))  # (1)!
#> pet=Cat(pet_type='cat', age=12)
  1. 有关模型页面中验证数据的更多信息。
from typing import Literal

from pydantic import BaseModel, Field


class Cat(BaseModel):
    pet_type: Literal['cat']
    age: int


class Dog(BaseModel):
    pet_type: Literal['dog']
    age: int


class Model(BaseModel):
    pet: Cat | Dog = Field(discriminator='pet_type')


print(Model.model_validate({'pet': {'pet_type': 'cat', 'age': 12}}))  # (1)!
#> pet=Cat(pet_type='cat', age=12)
  1. 有关模型页面中验证数据的更多信息。

以下示例展示了如何将 `discriminator` 关键字参数与 `Discriminator` 实例一起使用

from typing import Annotated, Literal, Union

from pydantic import BaseModel, Discriminator, Field, Tag


class Cat(BaseModel):
    pet_type: Literal['cat']
    age: int


class Dog(BaseModel):
    pet_kind: Literal['dog']
    age: int


def pet_discriminator(v):
    if isinstance(v, dict):
        return v.get('pet_type', v.get('pet_kind'))
    return getattr(v, 'pet_type', getattr(v, 'pet_kind', None))


class Model(BaseModel):
    pet: Union[Annotated[Cat, Tag('cat')], Annotated[Dog, Tag('dog')]] = Field(
        discriminator=Discriminator(pet_discriminator)
    )


print(repr(Model.model_validate({'pet': {'pet_type': 'cat', 'age': 12}})))
#> Model(pet=Cat(pet_type='cat', age=12))

print(repr(Model.model_validate({'pet': {'pet_kind': 'dog', 'age': 12}})))
#> Model(pet=Dog(pet_kind='dog', age=12))
from typing import Annotated, Literal

from pydantic import BaseModel, Discriminator, Field, Tag


class Cat(BaseModel):
    pet_type: Literal['cat']
    age: int


class Dog(BaseModel):
    pet_kind: Literal['dog']
    age: int


def pet_discriminator(v):
    if isinstance(v, dict):
        return v.get('pet_type', v.get('pet_kind'))
    return getattr(v, 'pet_type', getattr(v, 'pet_kind', None))


class Model(BaseModel):
    pet: Annotated[Cat, Tag('cat')] | Annotated[Dog, Tag('dog')] = Field(
        discriminator=Discriminator(pet_discriminator)
    )


print(repr(Model.model_validate({'pet': {'pet_type': 'cat', 'age': 12}})))
#> Model(pet=Cat(pet_type='cat', age=12))

print(repr(Model.model_validate({'pet': {'pet_kind': 'dog', 'age': 12}})))
#> Model(pet=Dog(pet_kind='dog', age=12))

您还可以利用 `Annotated` 来定义您的判别联合。有关更多详细信息,请参阅判别联合文档。

不可变性

参数 `frozen` 用于模拟冻结数据类的行为。它用于阻止在模型创建后(不可变性)为字段赋新值。

有关更多详细信息,请参阅冻结数据类文档

from pydantic import BaseModel, Field, ValidationError


class User(BaseModel):
    name: str = Field(frozen=True)
    age: int


user = User(name='John', age=42)

try:
    user.name = 'Jane'  # (1)!
except ValidationError as e:
    print(e)
    """
    1 validation error for User
    name
      Field is frozen [type=frozen_field, input_value='Jane', input_type=str]
    """
  1. 由于 `name` 字段已冻结,因此不允许赋值。

排除

`exclude` 参数可用于控制在导出模型时应从模型中排除哪些字段。

请看下面的例子

from pydantic import BaseModel, Field


class User(BaseModel):
    name: str
    age: int = Field(exclude=True)


user = User(name='John', age=42)
print(user.model_dump())  # (1)!
#> {'name': 'John'}
  1. 由于 `age` 字段被排除,因此它不包含在 `model_dump()` 输出中。

有关更多详细信息,请参阅 [序列化] 部分。

已弃用字段

`deprecated` 参数可用于将字段标记为已弃用。这样做会导致

  • 访问字段时发出运行时弃用警告。
  • 在生成的 JSON schema 中设置已弃用关键字。

此参数接受不同类型,如下所述。

deprecated 作为字符串

该值将用作弃用消息。

from typing import Annotated

from pydantic import BaseModel, Field


class Model(BaseModel):
    deprecated_field: Annotated[int, Field(deprecated='This is deprecated')]


print(Model.model_json_schema()['properties']['deprecated_field'])
#> {'deprecated': True, 'title': 'Deprecated Field', 'type': 'integer'}

通过 @warnings.deprecated 装饰器进行 deprecated

@warnings.deprecated 装饰器(或 Python 3.12 及更低版本上的 typing_extensions 反向移植)可以用作实例。

from typing import Annotated

from typing_extensions import deprecated

from pydantic import BaseModel, Field


class Model(BaseModel):
    deprecated_field: Annotated[int, deprecated('This is deprecated')]

    # Or explicitly using `Field`:
    alt_form: Annotated[int, Field(deprecated=deprecated('This is deprecated'))]
from typing import Annotated
from warnings import deprecated

from pydantic import BaseModel, Field


class Model(BaseModel):
    deprecated_field: Annotated[int, deprecated('This is deprecated')]

    # Or explicitly using `Field`:
    alt_form: Annotated[int, Field(deprecated=deprecated('This is deprecated'))]

支持 `category` 和 `stacklevel`

此功能的当前实现不考虑 `deprecated` 装饰器的 `category` 和 `stacklevel` 参数。这可能会在 Pydantic 的未来版本中实现。

deprecated 作为布尔值

from typing import Annotated

from pydantic import BaseModel, Field


class Model(BaseModel):
    deprecated_field: Annotated[int, Field(deprecated=True)]


print(Model.model_json_schema()['properties']['deprecated_field'])
#> {'deprecated': True, 'title': 'Deprecated Field', 'type': 'integer'}

在验证器中访问已弃用字段

在验证器中访问已弃用字段时,将发出弃用警告。您可以使用 catch_warnings 显式忽略它

import warnings

from typing_extensions import Self

from pydantic import BaseModel, Field, model_validator


class Model(BaseModel):
    deprecated_field: int = Field(deprecated='This is deprecated')

    @model_validator(mode='after')
    def validate_model(self) -> Self:
        with warnings.catch_warnings():
            warnings.simplefilter('ignore', DeprecationWarning)
            self.deprecated_field = self.deprecated_field * 2

自定义 JSON Schema

一些字段参数专门用于自定义生成的 JSON schema。这些参数是

  • title
  • 描述
  • 例子
  • json_schema_extra

在 JSON schema 文档的自定义 JSON Schema部分阅读更多关于使用字段自定义/修改 JSON schema 的信息。

computed_field 装饰器

API 文档

computed_field

computed_field 装饰器可用于在序列化模型或数据类时包含 propertycached_property 属性。该属性也将考虑在 JSON Schema 中(在序列化模式下)。

注意

属性对于从其他字段计算的字段,或者对于计算成本高昂的字段(因此,如果使用 cached_property,则会缓存)非常有用。

但是,请注意,Pydantic 不会对包装的属性执行任何额外的逻辑(验证、缓存失效等)。

这是一个带有计算字段的模型的 JSON schema(在序列化模式下)的示例

from pydantic import BaseModel, computed_field


class Box(BaseModel):
    width: float
    height: float
    depth: float

    @computed_field
    @property  # (1)!
    def volume(self) -> float:
        return self.width * self.height * self.depth


print(Box.model_json_schema(mode='serialization'))
"""
{
    'properties': {
        'width': {'title': 'Width', 'type': 'number'},
        'height': {'title': 'Height', 'type': 'number'},
        'depth': {'title': 'Depth', 'type': 'number'},
        'volume': {'readOnly': True, 'title': 'Volume', 'type': 'number'},
    },
    'required': ['width', 'height', 'depth', 'volume'],
    'title': 'Box',
    'type': 'object',
}
"""
  1. 如果未指定,computed_field 将隐式地将方法转换为 property。然而,为了类型检查的目的,最好明确使用 @property 装饰器。

这是一个使用 `model_dump` 方法和计算字段的示例

from pydantic import BaseModel, computed_field


class Box(BaseModel):
    width: float
    height: float
    depth: float

    @computed_field
    @property
    def volume(self) -> float:
        return self.width * self.height * self.depth


b = Box(width=1, height=2, depth=3)
print(b.model_dump())
#> {'width': 1.0, 'height': 2.0, 'depth': 3.0, 'volume': 6.0}

与常规字段一样,计算字段可以标记为已弃用

from typing_extensions import deprecated

from pydantic import BaseModel, computed_field


class Box(BaseModel):
    width: float
    height: float
    depth: float

    @computed_field
    @property
    @deprecated("'volume' is deprecated")
    def volume(self) -> float:
        return self.width * self.height * self.depth