跳转至

模型

API 文档

pydantic.main.BaseModel

在 Pydantic 中定义模式的主要方法之一是通过模型。模型只是从 pydantic.BaseModel 继承并将字段定义为注释属性的类。

你可以将模型视为类似于 C 语言中的结构体,或者视为 API 中的单个端点的要求。

模型与 Python 的数据类有很多相似之处,但经过了一些微妙但重要的设计差异,这些差异简化了与验证、序列化和 JSON 模式生成相关的某些工作流程。你可以在文档的“数据类”部分找到对此的更多讨论。

不可信的数据可以传递给模型,并且在解析和验证后,Pydantic 保证结果模型实例的字段将符合模型上定义的字段类型。

注意

“验证——故意的用词不当”### 简而言之

We use the term "validation" to refer to the process of instantiating a model (or other type) that adheres to specified types and constraints. This task, which Pydantic is well known for, is most widely recognized as "validation" in colloquial terms, even though in other contexts the term "validation" may be more restrictive.


The long version

The potential confusion around the term "validation" arises from the fact that, strictly speaking, Pydantic's primary focus doesn't align precisely with the dictionary definition of "validation":

validation

noun the action of checking or proving the validity or accuracy of something.

In Pydantic, the term "validation" refers to the process of instantiating a model (or other type) that adheres to specified types and constraints. Pydantic guarantees the types and constraints of the output, not the input data. This distinction becomes apparent when considering that Pydantic's ValidationError is raised when data cannot be successfully parsed into a model instance.

While this distinction may initially seem subtle, it holds practical significance. In some cases, "validation" goes beyond just model creation, and can include the copying and coercion of data. This can involve copying arguments passed to the constructor in order to perform coercion to a new type without mutating the original input data. For a more in-depth understanding of the implications for your usage, refer to the Data Conversion and Attribute Copies sections below.

In essence, Pydantic's primary goal is to assure that the resulting structure post-processing (termed "validation") precisely conforms to the applied type hints. Given the widespread adoption of "validation" as the colloquial term for this process, we will consistently use it in our documentation.

While the terms "parse" and "validation" were previously used interchangeably, moving forward, we aim to exclusively employ "validate", with "parse" reserved specifically for discussions related to JSON parsing.

基本模型用法

from pydantic import BaseModel


class User(BaseModel):
    id: int
    name: str = 'Jane Doe'

在这个例子中, User 是一个有两个字段的模型:

  • id ,这是一个整数,是必需的

  • name ,这是一个字符串,不是必需的(它有一个默认值)。

    user = User(id='123')

在这个例子中, userUser 的一个实例。对象的初始化将执行所有的解析和验证。如果没有抛出 ValidationError ,你就知道生成的模型实例是有效的。

assert user.id == 123
assert isinstance(user.id, int)
# Note that '123' was coerced to an int and its value is 123

有关 pydantic 的强制逻辑的更多详细信息可以在数据转换中找到。模型的字段可以像 user 对象的普通属性一样访问。根据字段类型,字符串 '123' 已转换为整数。

assert user.name == 'Jane Doe'

user 被初始化时, name 没有被设置,所以它有默认值。

assert user.model_fields_set == {'id'}

用户初始化时提供的字段。

assert user.model_dump() == {'id': 123, 'name': 'Jane Doe'}

或者 .model_dump()dict(user) 将提供一个字段字典,但 .model_dump() 可以接受许多其他参数。(请注意, dict(user) 不会递归地将嵌套模型转换为字典,但 .model_dump() 会。)

user.id = 321
assert user.id == 321

默认情况下,模型是可变的,可以通过属性赋值更改字段值。

模型方法和属性

上述示例仅展示了模型能够完成的工作的冰山一角。模型具有以下方法和属性:

注意

请参阅 BaseModel 以获取类定义,包括所有方法和属性的完整列表。

提示

请参阅迁移指南中对 pydantic.BaseModel 的更改,以获取 Pydantic V1 以来的更改详细信息。

嵌套模型

可以使用模型本身作为注释中的类型来定义更复杂的层次数据结构。

from typing import List, Optional

from pydantic import BaseModel


class Foo(BaseModel):
    count: int
    size: Optional[float] = None


class Bar(BaseModel):
    apple: str = 'x'
    banana: str = 'y'


class Spam(BaseModel):
    foo: Foo
    bars: List[Bar]


m = Spam(foo={'count': 4}, bars=[{'apple': 'x1'}, {'apple': 'x2'}])
print(m)
"""
foo=Foo(count=4, size=None) bars=[Bar(apple='x1', banana='y'), Bar(apple='x2', banana='y')]
"""
print(m.model_dump())
"""
{
    'foo': {'count': 4, 'size': None},
    'bars': [{'apple': 'x1', 'banana': 'y'}, {'apple': 'x2', 'banana': 'y'}],
}
"""

对于自引用模型,请参见推迟的注释。

注意

在定义模型时,要注意字段名与其类型、之前定义的模型或导入的库之间的命名冲突。

For example, the following would yield a validation error:

from typing import Optional

from pydantic import BaseModel


class Boo(BaseModel):
    int: Optional[int] = None


m = Boo(int=123)  # errors
An error occurs since the field int is set to a default value of None and has the exact same name as its type, so both are interpreted to be None.

重建模型模式

可以使用 model_rebuild() 重建模型模式。这对于构建递归通用模型很有用。

from pydantic import BaseModel, PydanticUserError


class Foo(BaseModel):
    x: 'Bar'


try:
    Foo.model_json_schema()
except PydanticUserError as e:
    print(e)
    """
    `Foo` is not fully defined; you should define `Bar`, then call `Foo.model_rebuild()`.

    For further information visit https://errors.pydantic.dev/2/u/class-not-fully-defined
    """


class Bar(BaseModel):
    pass


Foo.model_rebuild()
print(Foo.model_json_schema())
"""
{
    '$defs': {'Bar': {'properties': {}, 'title': 'Bar', 'type': 'object'}},
    'properties': {'x': {'$ref': '#/$defs/Bar'}},
    'required': ['x'],
    'title': 'Foo',
    'type': 'object',
}
"""

Pydantic 尝试自动确定何时需要这样做,如果没有这样做,它会报错,但在处理递归模型或泛型时,您可能希望主动调用 model_rebuild()

在 V2 中,[model_rebuild()][pydantic.main.BaseModel.model_rebuild] 取代了 V1 中的 [update_forward_refs()]。新行为有一些细微的差异。最大的变化是,在最外层模型上调用 [model_rebuild()][pydantic.main.BaseModel.model_rebuild] 时,它会构建一个用于验证整个模型(嵌套模型和所有模型)的核心模式,因此在调用 [model_rebuild()][pydantic.main.BaseModel.model_rebuild] 之前,所有类型都需要准备好。

任意类实例

(原名“ORM 模式”/ from_orm 。)

Pydantic 模型也可以通过读取与模型字段名称相对应的实例属性来从任意类实例创建。此功能的一个常见应用是与对象关系映射(ORM)集成。

为此,请设置 config 属性 model_config['from_attributes'] = True 。有关更多信息,请参阅 模型配置ConfigDict

这里的示例使用了 SQLAlchemy,但同样的方法应该适用于任何 ORM。

from typing import List

from sqlalchemy import Column, Integer, String
from sqlalchemy.dialects.postgresql import ARRAY
from sqlalchemy.orm import declarative_base
from typing_extensions import Annotated

from pydantic import BaseModel, ConfigDict, StringConstraints

Base = declarative_base()


class CompanyOrm(Base):
    __tablename__ = 'companies'

    id = Column(Integer, primary_key=True, nullable=False)
    public_key = Column(String(20), index=True, nullable=False, unique=True)
    name = Column(String(63), unique=True)
    domains = Column(ARRAY(String(255)))


class CompanyModel(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    id: int
    public_key: Annotated[str, StringConstraints(max_length=20)]
    name: Annotated[str, StringConstraints(max_length=63)]
    domains: List[Annotated[str, StringConstraints(max_length=255)]]


co_orm = CompanyOrm(
    id=123,
    public_key='foobar',
    name='Testing',
    domains=['example.com', 'foobar.com'],
)
print(co_orm)
#> <__main__.CompanyOrm object at 0x0123456789ab>
co_model = CompanyModel.model_validate(co_orm)
print(co_model)
"""
id=123 public_key='foobar' name='Testing' domains=['example.com', 'foobar.com']
"""

保留名称

你可能想根据保留的 SQLAlchemy 字段给 Column 命名。在这种情况下, Field 别名会很方便:

import typing

import sqlalchemy as sa
from sqlalchemy.orm import declarative_base

from pydantic import BaseModel, ConfigDict, Field


class MyModel(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    metadata: typing.Dict[str, str] = Field(alias='metadata_')


Base = declarative_base()


class SQLModel(Base):
    __tablename__ = 'my_table'
    id = sa.Column('id', sa.Integer, primary_key=True)
    # 'metadata' is reserved by SQLAlchemy, hence the '_'
    metadata_ = sa.Column('metadata', sa.JSON)


sql_model = SQLModel(metadata_={'key': 'val'}, id=1)

pydantic_model = MyModel.model_validate(sql_model)

print(pydantic_model.model_dump())
#> {'metadata': {'key': 'val'}}
print(pydantic_model.model_dump(by_alias=True))
#> {'metadata_': {'key': 'val'}}

注意

上面的示例之所以有效,是因为别名在字段填充方面优先于字段名称。访问 SQLModelmetadata 属性将导致一个 ValidationError

嵌套属性

当使用属性来解析模型时,模型实例将根据需要从顶级属性和更深嵌套的属性中创建。

以下是一个示例,演示了该原理:

from typing import List

from pydantic import BaseModel, ConfigDict


class PetCls:
    def __init__(self, *, name: str, species: str):
        self.name = name
        self.species = species


class PersonCls:
    def __init__(self, *, name: str, age: float = None, pets: List[PetCls]):
        self.name = name
        self.age = age
        self.pets = pets


class Pet(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    name: str
    species: str


class Person(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    name: str
    age: float = None
    pets: List[Pet]


bones = PetCls(name='Bones', species='dog')
orion = PetCls(name='Orion', species='cat')
anna = PersonCls(name='Anna', age=20, pets=[bones, orion])
anna_model = Person.model_validate(anna)
print(anna_model)
"""
name='Anna' age=20.0 pets=[Pet(name='Bones', species='dog'), Pet(name='Orion', species='cat')]
"""

错误处理

当 Pydantic 在验证数据时发现错误时,它将引发 ValidationError

无论发现多少个错误,都会引发一个类型为 ValidationError 的单个异常,而 ValidationError 将包含有关所有错误及其发生方式的信息。

请参阅错误处理,以获取有关标准和自定义错误的详细信息。

作为一个示范:

from typing import List

from pydantic import BaseModel, ValidationError


class Model(BaseModel):
    list_of_ints: List[int]
    a_float: float


data = dict(
    list_of_ints=['1', 2, 'bad'],
    a_float='not a float',
)

try:
    Model(**data)
except ValidationError as e:
    print(e)
    """
    2 validation errors for Model
    list_of_ints.2
      Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='bad', input_type=str]
    a_float
      Input should be a valid number, unable to parse string as a number [type=float_parsing, input_value='not a float', input_type=str]
    """

辅助功能

Pydantic 在模型上提供了三个用于解析数据的 classmethod 帮助函数:

  • model_validate() : 这与模型的 __init__ 方法非常相似,只是它接受一个字典或对象而不是关键字参数。如果传递的对象无法验证,或者它不是要验证的模型的字典或实例,则会引发 ValidationError

  • model_validate_json() : 此方法接受一个 str 或 bytes,并将其解析为 json,然后将结果传递给 model_validate()

  • model_validate_strings() : 此方法接受一个具有字符串键和值的字典(可以嵌套),并以 JSON 模式验证数据,以便将所述字符串强制转换为正确的类型。

from datetime import datetime
from typing import Optional

from pydantic import BaseModel, ValidationError


class User(BaseModel):
    id: int
    name: str = 'John Doe'
    signup_ts: Optional[datetime] = None


m = User.model_validate({'id': 123, 'name': 'James'})
print(m)
#> id=123 name='James' signup_ts=None

try:
    User.model_validate(['not', 'a', 'dict'])
except ValidationError as e:
    print(e)
    """
    1 validation error for User
      Input should be a valid dictionary or instance of User [type=model_type, input_value=['not', 'a', 'dict'], input_type=list]
    """

m = User.model_validate_json('{"id": 123, "name": "James"}')
print(m)
#> id=123 name='James' signup_ts=None

try:
    m = User.model_validate_json('{"id": 123, "name": 123}')
except ValidationError as e:
    print(e)
    """
    1 validation error for User
    name
      Input should be a valid string [type=string_type, input_value=123, input_type=int]
    """

try:
    m = User.model_validate_json('invalid JSON')
except ValidationError as e:
    print(e)
    """
    1 validation error for User
      Invalid JSON: expected value at line 1 column 1 [type=json_invalid, input_value='invalid JSON', input_type=str]
    """

m = User.model_validate_strings({'id': '123', 'name': 'James'})
print(m)
#> id=123 name='James' signup_ts=None

m = User.model_validate_strings(
    {'id': '123', 'name': 'James', 'signup_ts': '2024-04-01T12:00:00'}
)
print(m)
#> id=123 name='James' signup_ts=datetime.datetime(2024, 4, 1, 12, 0)

try:
    m = User.model_validate_strings(
        {'id': '123', 'name': 'James', 'signup_ts': '2024-04-01'}, strict=True
    )
except ValidationError as e:
    print(e)
    """
    1 validation error for User
    signup_ts
      Input should be a valid datetime, invalid datetime separator, expected `T`, `t`, `_` or space [type=datetime_parsing, input_value='2024-04-01', input_type=str]
    """

如果你想验证不是 JSON 格式的序列化数据,你应该自己将数据加载到字典中,然后将其传递给 model_validate

注意

根据涉及的类型和模型配置, model_validate model_validate_json 可能具有不同的验证行为。如果您的数据来自非 JSON 源,但希望获得与 model_validate_json 相同的验证行为和错误,我们目前的建议是使用 model_validate_json(json.dumps(data)) ,或者如果数据采用具有字符串键和值的(可能嵌套的)字典形式,则使用 model_validate_strings

注意

在文档的 JSON 部分了解更多关于 JSON 解析的信息。

注意

如果您正在将模型的实例传递给 model_validate ,则需要考虑在模型的配置中设置 revalidate_instances 。如果您不设置此值,则将跳过模型实例的验证。请参见下面的示例:

:x: revalidate_instances='never' " ```py from pydantic import BaseModel

class Model(BaseModel):
    a: int


m = Model(a=0)
# note: the `model_config` setting validate_assignment=True` can prevent this kind of misbehavior
m.a = 'not an int'

# doesn't raise a validation error even though m is invalid
m2 = Model.model_validate(m)
```

"✅ revalidate_instances='always' " ```py from pydantic import BaseModel, ConfigDict, ValidationError

class Model(BaseModel):
    a: int

    model_config = ConfigDict(revalidate_instances='always')


m = Model(a=0)
# note: the `model_config` setting validate_assignment=True` can prevent this kind of misbehavior
m.a = 'not an int'

try:
    m2 = Model.model_validate(m)
except ValidationError as e:
    print(e)
    """
    1 validation error for Model
    a
      Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='not an int', input_type=str]
    """
```

创建没有验证的模型

Pydantic 还提供了 model_construct() 方法,该方法允许在不进行验证的情况下创建模型。这在至少几种情况下可能很有用:

  • 当处理已知有效的复杂数据(出于性能原因)时

  • 当一个或多个验证器函数不是幂等的,或者

  • 当一个或多个验证器函数有你不想触发的副作用时。

注意

在 Pydantic V2 中, BaseModel.__init__BaseModel.model_construct 之间的性能差距已经大大缩小。对于简单的模型,调用 BaseModel.__init__ 甚至可能更快。如果您出于性能原因正在使用 model_construct() ,在假定 model_construct() 更快之前,您可能需要对您的用例进行分析。

警告

model_construct() 不进行任何验证,这意味着它可以创建无效的模型。您应该只在已经验证过的数据或您绝对信任的数据上使用 model_construct() 方法。

from pydantic import BaseModel


class User(BaseModel):
    id: int
    age: int
    name: str = 'John Doe'


original_user = User(id=123, age=32)

user_data = original_user.model_dump()
print(user_data)
#> {'id': 123, 'age': 32, 'name': 'John Doe'}
fields_set = original_user.model_fields_set
print(fields_set)
#> {'age', 'id'}

# ...
# pass user_data and fields_set to RPC or save to the database etc.
# ...

# you can then create a new instance of User without
# re-running validation which would be unnecessary at this point:
new_user = User.model_construct(_fields_set=fields_set, **user_data)
print(repr(new_user))
#> User(id=123, age=32, name='John Doe')
print(new_user.model_fields_set)
#> {'age', 'id'}

# construct can be dangerous, only use it with validated data!:
bad_user = User.model_construct(id='dog')
print(repr(bad_user))
#> User(id='dog', name='John Doe')

@pydantic.main.BaseModel.model_construct 的 _fields_set 关键字参数是可选的,但它允许你更精确地了解哪些字段最初被设置,哪些没有。如果省略了它,@pydantic.main.BaseModel.model_fields_set 就只是提供的数据的键。

例如,在上例中,如果没有提供 _fields_set ,则 new_user.model_fields_set 将是 {'id', 'age', 'name'}

请注意,对于 RootModel 的子类,可以将根值传递给 model_construct() 的位置,而不是使用关键字参数。

以下是关于 model_construct() 行为的一些其他注意事项:

  • 当我们说“没有进行验证”时——这包括将字典转换为模型实例。因此,如果您有一个字段类型为 Model ,那么在将内部字典传递给 model_construct() 之前,您需要自己将其转换为模型。

  • 如果不为具有默认值的字段传递关键字参数,则仍将使用默认值。

  • 对于具有私有属性的模型,将像调用 __init__ 时一样初始化 __pydantic_private__ 字典。

  • 当使用 model_construct() 构建实例时,不会调用模型或其任何父类中的 __init__ 方法,即使定义了自定义 __init__ 方法也是如此。

注意

“关于 extramodel_construct 的行为” * 对于具有 model_config['extra'] == 'allow' 的模型,与字段不对应的数据将正确存储在 __pydantic_extra__ 字典中,并保存到模型的 __dict__ 中。 * 对于具有 model_config['extra'] == 'ignore' 的模型,与字段不对应的数据将被忽略-即不会存储在 __pydantic_extra____dict__ 实例上。 * 与对 __init__ 的调用不同,对具有 model_config['extra'] == 'forbid'model_construct 的调用在存在与字段不对应的数据时不会引发错误。相反,将简单地忽略所述输入数据。

通用模型

Pydantic 支持创建泛型模型,以更轻松地重用通用模型结构。

为了声明泛型模型,你执行以下步骤:

  1. 声明一个或多个 typing.TypeVar 实例来用于参数化模型。

  2. 声明一个从 pydantic.BaseModeltyping.Generic 继承的 pydantic 模型,其中你将 TypeVar 实例作为参数传递给 typing.Generic

  3. 使用 TypeVar 实例作为注释,在需要用其他类型或 pydantic 模型替换它们的地方使用。

以下是一个使用通用的 BaseModel 子类创建易于重用的 HTTP 响应有效负载包装器的示例:

from typing import Generic, List, Optional, TypeVar

from pydantic import BaseModel, ValidationError

DataT = TypeVar('DataT')


class DataModel(BaseModel):
    numbers: List[int]
    people: List[str]


class Response(BaseModel, Generic[DataT]):
    data: Optional[DataT] = None


print(Response[int](data=1))
#> data=1
print(Response[str](data='value'))
#> data='value'
print(Response[str](data='value').model_dump())
#> {'data': 'value'}

data = DataModel(numbers=[1, 2, 3], people=[])
print(Response[DataModel](data=data).model_dump())
#> {'data': {'numbers': [1, 2, 3], 'people': []}}
try:
    Response[int](data='value')
except ValidationError as e:
    print(e)
    """
    1 validation error for Response[int]
    data
      Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='value', input_type=str]
    """

如果你在泛型模型定义中设置了model_config或使用了@field_validator或其他 Pydantic 装饰器,它们将以与从BaseModel子类继承时相同的方式应用于参数化子类。你在泛型类上定义的任何方法也将被继承。

Pydantic 的泛型也与类型检查器正确集成,因此如果为每个参数化声明一个不同的类型,您将获得您所期望的所有类型检查。

注意

在内部,当泛型模型被参数化时,Pydantic 在运行时创建 BaseModel 的子类。这些类被缓存,因此使用泛型模型引入的开销应该很小。

要从泛型模型继承并保留它是泛型的事实,子类也必须从 typing.Generic 继承:

from typing import Generic, TypeVar

from pydantic import BaseModel

TypeX = TypeVar('TypeX')


class BaseClass(BaseModel, Generic[TypeX]):
    X: TypeX


class ChildClass(BaseClass[TypeX], Generic[TypeX]):
    # Inherit from Generic[TypeX]
    pass


# Replace TypeX by int
print(ChildClass[int](X=1))
#> X=1

你还可以创建一个 BaseModel 的泛型子类,该子类部分或完全替换了父类中的类型参数:

from typing import Generic, TypeVar

from pydantic import BaseModel

TypeX = TypeVar('TypeX')
TypeY = TypeVar('TypeY')
TypeZ = TypeVar('TypeZ')


class BaseClass(BaseModel, Generic[TypeX, TypeY]):
    x: TypeX
    y: TypeY


class ChildClass(BaseClass[int, TypeY], Generic[TypeY, TypeZ]):
    z: TypeZ


# Replace TypeY by str
print(ChildClass[str, int](x='1', y='y', z='3'))
#> x=1 y='y' z=3

如果具体子类的名称很重要,也可以覆盖默认的名称生成:

from typing import Any, Generic, Tuple, Type, TypeVar

from pydantic import BaseModel

DataT = TypeVar('DataT')


class Response(BaseModel, Generic[DataT]):
    data: DataT

    @classmethod
    def model_parametrized_name(cls, params: Tuple[Type[Any], ...]) -> str:
        return f'{params[0].__name__.title()}Response'


print(repr(Response[int](data=1)))
#> IntResponse(data=1)
print(repr(Response[str](data='a')))
#> StrResponse(data='a')

你可以在其他模型中使用参数化泛型模型作为类型:

from typing import Generic, TypeVar

from pydantic import BaseModel

T = TypeVar('T')


class ResponseModel(BaseModel, Generic[T]):
    content: T


class Product(BaseModel):
    name: str
    price: float


class Order(BaseModel):
    id: int
    product: ResponseModel[Product]


product = Product(name='Apple', price=0.5)
response = ResponseModel[Product](content=product)
order = Order(id=1, product=response)
print(repr(order))
"""
Order(id=1, product=ResponseModel[Product](content=Product(name='Apple', price=0.5)))
"""

提示

当在另一个模型中使用参数化泛型模型作为类型(如 product: ResponseModel[Product] )时,请确保在初始化模型实例时对该泛型模型进行参数化(如 response = ResponseModel[Product](content=product) )。如果不这样做,将引发 ValidationError ,因为 Pydantic 不会根据传递给它的数据推断泛型模型的类型。

在嵌套模型中使用相同的 TypeVar 可以在模型的不同点强制实施类型关系:

from typing import Generic, TypeVar

from pydantic import BaseModel, ValidationError

T = TypeVar('T')


class InnerT(BaseModel, Generic[T]):
    inner: T


class OuterT(BaseModel, Generic[T]):
    outer: T
    nested: InnerT[T]


nested = InnerT[int](inner=1)
print(OuterT[int](outer=1, nested=nested))
#> outer=1 nested=InnerT[int](inner=1)
try:
    nested = InnerT[str](inner='a')
    print(OuterT[int](outer='a', nested=nested))
except ValidationError as e:
    print(e)
    """
    2 validation errors for OuterT[int]
    outer
      Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str]
    nested
      Input should be a valid dictionary or instance of InnerT[int] [type=model_type, input_value=InnerT[str](inner='a'), input_type=InnerT[str]]
    """

当使用绑定类型参数时,以及当不指定类型参数时,Pydantic 对泛型模型的处理类似于对内置泛型类型如 ListDict 的处理:

  • 如果在实例化泛型模型之前没有指定参数,它们将被验证为 TypeVar 的绑定。

  • 如果涉及的 TypeVar 没有界限,则将其视为 Any

此外,与 ListDict 一样,使用 TypeVar 指定的任何参数稍后都可以替换为具体类型:

from typing import Generic, TypeVar

from pydantic import BaseModel, ValidationError

AT = TypeVar('AT')
BT = TypeVar('BT')


class Model(BaseModel, Generic[AT, BT]):
    a: AT
    b: BT


print(Model(a='a', b='a'))
#> a='a' b='a'

IntT = TypeVar('IntT', bound=int)
typevar_model = Model[int, IntT]
print(typevar_model(a=1, b=1))
#> a=1 b=1
try:
    typevar_model(a='a', b='a')
except ValidationError as exc:
    print(exc)
    """
    2 validation errors for Model[int, TypeVar]
    a
      Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str]
    b
      Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str]
    """

concrete_model = typevar_model[int]
print(concrete_model(a=1, b=1))
#> a=1 b=1

警告

虽然它可能不会引发错误,但我们强烈建议不要在 isinstance 检查中使用参数化泛型。

For example, you should not do isinstance(my_model, MyGenericModel[int]). However, it is fine to do isinstance(my_model, MyGenericModel). (Note that, for standard generics, it would raise an error to do a subclass check with a parameterized generic.)

If you need to perform isinstance checks against parametrized generics, you can do this by subclassing the parametrized generic class. This looks like class MyIntModel(MyGenericModel[int]): ... and isinstance(my_model, MyIntModel).

如果在 TypeVar 绑定中使用 Pydantic 模型,并且泛型类型从未参数化,那么 Pydantic 将使用该绑定进行验证,但在序列化方面将该值视为 Any

from typing import Generic, Optional, TypeVar

from pydantic import BaseModel


class ErrorDetails(BaseModel):
    foo: str


ErrorDataT = TypeVar('ErrorDataT', bound=ErrorDetails)


class Error(BaseModel, Generic[ErrorDataT]):
    message: str
    details: Optional[ErrorDataT]


class MyErrorDetails(ErrorDetails):
    bar: str


# serialized as Any
error = Error(
    message='We just had an error',
    details=MyErrorDetails(foo='var', bar='var2'),
)
assert error.model_dump() == {
    'message': 'We just had an error',
    'details': {
        'foo': 'var',
        'bar': 'var2',
    },
}

# serialized using the concrete parametrization
# note that `'bar': 'var2'` is missing
error = Error[ErrorDetails](
    message='We just had an error',
    details=ErrorDetails(foo='var'),
)
assert error.model_dump() == {
    'message': 'We just had an error',
    'details': {
        'foo': 'var',
    },
}
以下是上述行为的另一个示例,枚举了关于绑定规范和泛型类型参数化的所有排列:

from typing import Generic

from typing_extensions import TypeVar

from pydantic import BaseModel

TBound = TypeVar('TBound', bound=BaseModel)
TNoBound = TypeVar('TNoBound')


class IntValue(BaseModel):
    value: int


class ItemBound(BaseModel, Generic[TBound]):
    item: TBound


class ItemNoBound(BaseModel, Generic[TNoBound]):
    item: TNoBound


item_bound_inferred = ItemBound(item=IntValue(value=3))
item_bound_explicit = ItemBound[IntValue](item=IntValue(value=3))
item_no_bound_inferred = ItemNoBound(item=IntValue(value=3))
item_no_bound_explicit = ItemNoBound[IntValue](item=IntValue(value=3))

# calling `print(x.model_dump())` on any of the above instances results in the following:
#> {'item': {'value': 3}}

如果你使用 default=... (在 Python >= 3.13 中可用或通过 typing-extensions )或约束( TypeVar('T', str, int) ;请注意,你很少希望使用这种形式的 TypeVar ),那么如果类型变量没有参数化,那么默认值或约束将同时用于验证和序列化。你可以使用 pydantic.SerializeAsAny 覆盖此行为:

from typing import Generic, Optional

from typing_extensions import TypeVar

from pydantic import BaseModel, SerializeAsAny


class ErrorDetails(BaseModel):
    foo: str


ErrorDataT = TypeVar('ErrorDataT', default=ErrorDetails)


class Error(BaseModel, Generic[ErrorDataT]):
    message: str
    details: Optional[ErrorDataT]


class MyErrorDetails(ErrorDetails):
    bar: str


# serialized using the default's serializer
error = Error(
    message='We just had an error',
    details=MyErrorDetails(foo='var', bar='var2'),
)
assert error.model_dump() == {
    'message': 'We just had an error',
    'details': {
        'foo': 'var',
    },
}


class SerializeAsAnyError(BaseModel, Generic[ErrorDataT]):
    message: str
    details: Optional[SerializeAsAny[ErrorDataT]]


# serialized as Any
error = SerializeAsAnyError(
    message='We just had an error',
    details=MyErrorDetails(foo='var', bar='baz'),
)
assert error.model_dump() == {
    'message': 'We just had an error',
    'details': {
        'foo': 'var',
        'bar': 'baz',
    },
}

注意,如果在针对泛型的边界进行验证时不参数化泛型,可能会遇到一些问题,因为这可能导致数据丢失。请参见下面的示例:

from typing import Generic

from typing_extensions import TypeVar

from pydantic import BaseModel

TItem = TypeVar('TItem', bound='ItemBase')


class ItemBase(BaseModel): ...


class IntItem(ItemBase):
    value: int


class ItemHolder(BaseModel, Generic[TItem]):
    item: TItem


loaded_data = {'item': {'value': 1}}


print(ItemHolder(**loaded_data).model_dump())  # (1)!
#> {'item': {}}

print(ItemHolder[IntItem](**loaded_data).model_dump())  # (2)!
#> {'item': {'value': 1}}
  1. 当泛型没有参数化时,输入数据将根据泛型边界进行验证。由于 ItemBase 没有字段,因此会丢失 item 字段信息。

  2. 在这种情况下,运行时类型信息是通过泛型参数化显式提供的,因此输入数据将根据 IntItem 类进行验证,并且序列化输出与预期的匹配。

Dynamic model creation

动态模型创建

API 文档

pydantic.main.create_model

在某些情况下,希望使用运行时信息创建模型来指定字段。为此,Pydantic 提供了 create_model 函数,允许动态创建模型:

from pydantic import BaseModel, create_model

DynamicFoobarModel = create_model(
    'DynamicFoobarModel', foo=(str, ...), bar=(int, 123)
)


class StaticFoobarModel(BaseModel):
    foo: str
    bar: int = 123

这里的 StaticFoobarModelDynamicFoobarModel 是相同的。

字段由以下元组形式之一定义:

  • (<type>, <default value>)
  • (<type>, Field(...))
  • typing.Annotated[<type>, Field(...)]

使用 Field(...) 调用作为元组中的第二个参数(默认值)可以进行更高级的字段配置。因此,以下是类似的:

from pydantic import BaseModel, Field, create_model

DynamicModel = create_model(
    'DynamicModel',
    foo=(str, Field(..., description='foo description', alias='FOO')),
)


class StaticModel(BaseModel):
    foo: str = Field(..., description='foo description', alias='FOO')

可以使用特殊的关键字参数 __config____base__ 来定制新模型。这包括使用额外的字段扩展基础模型。

from pydantic import BaseModel, create_model


class FooModel(BaseModel):
    foo: str
    bar: int = 123


BarModel = create_model(
    'BarModel',
    apple=(str, 'russet'),
    banana=(str, 'yellow'),
    __base__=FooModel,
)
print(BarModel)
#> <class '__main__.BarModel'>
print(BarModel.model_fields.keys())
#> dict_keys(['foo', 'bar', 'apple', 'banana'])

你还可以通过将有效的验证器传递给 __validators__ 参数来添加验证器。

from pydantic import ValidationError, create_model, field_validator


def username_alphanumeric(cls, v):
    assert v.isalnum(), 'must be alphanumeric'
    return v


validators = {
    'username_validator': field_validator('username')(username_alphanumeric)
}

UserModel = create_model(
    'UserModel', username=(str, ...), __validators__=validators
)

user = UserModel(username='scolvin')
print(user)
#> username='scolvin'

try:
    UserModel(username='scolvi%n')
except ValidationError as e:
    print(e)
    """
    1 validation error for UserModel
    username
      Assertion failed, must be alphanumeric [type=assertion_error, input_value='scolvi%n', input_type=str]
    """

注意

要腌制动态创建的模型:

  • the model must be defined globally
  • it must provide __module__

RootModel and custom root types

RootModel 和自定义根类型

API 文档

pydantic.root_model.RootModel

Pydantic 模型可以通过继承 pydantic.RootModel 来定义一个“自定义根类型”。

根类型可以是 Pydantic 支持的任何类型,并通过泛型参数指定为 RootModel 。根值可以通过第一个也是唯一的参数传递给模型 __init__ model_validate

以下是一个示例,说明其工作原理:

from typing import Dict, List

from pydantic import RootModel

Pets = RootModel[List[str]]
PetsByName = RootModel[Dict[str, str]]


print(Pets(['dog', 'cat']))
#> root=['dog', 'cat']
print(Pets(['dog', 'cat']).model_dump_json())
#> ["dog","cat"]
print(Pets.model_validate(['dog', 'cat']))
#> root=['dog', 'cat']
print(Pets.model_json_schema())
"""
{'items': {'type': 'string'}, 'title': 'RootModel[List[str]]', 'type': 'array'}
"""

print(PetsByName({'Otis': 'dog', 'Milo': 'cat'}))
#> root={'Otis': 'dog', 'Milo': 'cat'}
print(PetsByName({'Otis': 'dog', 'Milo': 'cat'}).model_dump_json())
#> {"Otis":"dog","Milo":"cat"}
print(PetsByName.model_validate({'Otis': 'dog', 'Milo': 'cat'}))
#> root={'Otis': 'dog', 'Milo': 'cat'}

如果你想直接访问 root 字段中的项目或迭代这些项目,可以实现自定义的 __iter____getitem__ 函数,如下例所示。

from typing import List

from pydantic import RootModel


class Pets(RootModel):
    root: List[str]

    def __iter__(self):
        return iter(self.root)

    def __getitem__(self, item):
        return self.root[item]


pets = Pets.model_validate(['dog', 'cat'])
print(pets[0])
#> dog
print([pet for pet in pets])
#> ['dog', 'cat']

你还可以直接创建参数化根模型的子类:

from typing import List

from pydantic import RootModel


class Pets(RootModel[List[str]]):
    def describe(self) -> str:
        return f'Pets: {", ".join(self.root)}'


my_pets = Pets.model_validate(['dog', 'cat'])

print(my_pets.describe())
#> Pets: dog, cat

虚假不变性

可以通过 model_config['frozen'] = True 将模型配置为不可变。设置此选项后,尝试更改实例属性的值将引发错误。有关更多详细信息,请参阅 API 参考

注意

这种行为是在 Pydantic V1 中通过配置设置 allow_mutation = False 实现的。此配置标志在 Pydantic V2 中已弃用,并已被 frozen 取代。

警告

在 Python 中,并不强制实施不可变性。如果开发人员选择这样做,他们有能力修改通常被认为是“不可变”的对象。

from pydantic import BaseModel, ConfigDict, ValidationError


class FooBarModel(BaseModel):
    model_config = ConfigDict(frozen=True)

    a: str
    b: dict


foobar = FooBarModel(a='hello', b={'apple': 'pear'})

try:
    foobar.a = 'different'
except ValidationError as e:
    print(e)
    """
    1 validation error for FooBarModel
    a
      Instance is frozen [type=frozen_instance, input_value='different', input_type=str]
    """

print(foobar.a)
#> hello
print(foobar.b)
#> {'apple': 'pear'}
foobar.b['apple'] = 'grape'
print(foobar.b)
#> {'apple': 'grape'}

尝试更改 a 导致错误, a 保持不变。但是, b 是可变的, foobar 的不可变性并没有阻止 b 被更改。

抽象基类

Pydantic 模型可以与 Python 的抽象基类 (ABCs) 一起使用。

import abc

from pydantic import BaseModel


class FooBarModel(BaseModel, abc.ABC):
    a: str
    b: int

    @abc.abstractmethod
    def my_abstract_method(self):
        pass

字段顺序

字段顺序对模型的影响如下:

  • 模型模式中保留了字段顺序

  • 验证错误中保留了字段顺序

  • 通过 .model_dump().model_dump_json() 等保留字段顺序。

from pydantic import BaseModel, ValidationError


class Model(BaseModel):
    a: int
    b: int = 2
    c: int = 1
    d: int = 0
    e: float


print(Model.model_fields.keys())
#> dict_keys(['a', 'b', 'c', 'd', 'e'])
m = Model(e=2, a=1)
print(m.model_dump())
#> {'a': 1, 'b': 2, 'c': 1, 'd': 0, 'e': 2.0}
try:
    Model(a='x', b='x', c='x', d='x', e='x')
except ValidationError as err:
    error_locations = [e['loc'] for e in err.errors()]

print(error_locations)
#> [('a',), ('b',), ('c',), ('d',), ('e',)]

必填字段

要声明一个字段为必填字段,您可以使用注解或注解与 Field 规范结合使用。您也可以使用 Ellipsis / ... 来强调一个字段是必填的,特别是在使用 Field 构造函数时。

Field 函数主要用于为属性配置设置,如 aliasdescription 。构造函数支持 Ellipsis / ... 作为唯一的位置参数。这用于表示该字段是必填的,尽管是类型提示强制执行此要求。

from pydantic import BaseModel, Field


class Model(BaseModel):
    a: int
    b: int = ...
    c: int = Field(..., alias='C')

这里 abc 都是必需的。然而,这种对 b: int = ... 的使用在 mypy 中不能正常工作,并且截至 v1.0 应在大多数情况下避免使用。

注意

在 Pydantic V1 中,用 OptionalAny 注释的字段即使没有显式指定默认值,也会被赋予隐式默认值 None 。这种行为在 Pydantic V2 中已经改变,不再有任何类型注释会导致字段具有隐式默认值。

See the migration guide for more details on changes to required and nullable fields.

具有不可哈希默认值的字段

Python 中常见的错误来源之一是将可变对象用作函数或方法参数的默认值,因为在每次调用中都会重用同一个实例。

在这种情况下, dataclasses 模块实际上会引发错误,提示你应该使用 default_factory 参数来调用 dataclasses.field

Pydantic 还支持使用 default_factory 作为不可哈希的默认值,但这不是必需的。如果默认值不可哈希,Pydantic 在创建模型的每个实例时将对默认值进行深拷贝:

from typing import Dict, List

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)
#> [{}]

具有动态默认值的字段

当声明一个带有默认值的字段时,您可能希望它是动态的(即对于每个模型都不同)。为此,您可能希望使用 default_factory 。 这是一个例子: 译文:

from datetime import datetime, timezone
from uuid import UUID, uuid4

from pydantic import BaseModel, Field


def datetime_now() -> datetime:
    return datetime.now(timezone.utc)


class Model(BaseModel):
    uid: UUID = Field(default_factory=uuid4)
    updated: datetime = Field(default_factory=datetime_now)


m1 = Model()
m2 = Model()
assert m1.uid != m2.uid

你可以在 Field 函数的文档中找到更多信息。

自动排除的属性

类变量

使用 typing.ClassVar 注释的属性将被 Pydantic 正确地视为类变量,并且不会成为模型实例上的字段:

from typing import ClassVar

from pydantic import BaseModel


class Model(BaseModel):
    x: int = 2
    y: ClassVar[int] = 1


m = Model()
print(m)
#> x=2
print(Model.y)
#> 1

Private model attributes

私有模型属性

API Documentation

pydantic.fields.PrivateAttr

API 文档

pydantic.fields.PrivateAttr

具有前导下划线的属性名称不会被 Pydantic 视为字段,也不会包含在模型模式中。相反,这些会被转换为“私有属性”,在调用 __init__model_validate 等时不会进行验证,甚至不会设置。

注意

截至 Pydantic v2.1.0,如果尝试使用 Field 函数与私有属性一起使用,将收到 NameError。因为私有属性不作为字段处理,所以无法应用 Field() 函数。

以下是一个使用示例:

from datetime import datetime
from random import randint

from pydantic import BaseModel, PrivateAttr


class TimeAwareModel(BaseModel):
    _processed_at: datetime = PrivateAttr(default_factory=datetime.now)
    _secret_value: str

    def __init__(self, **data):
        super().__init__(**data)
        # this could also be done with default_factory
        self._secret_value = randint(1, 5)


m = TimeAwareModel()
print(m._processed_at)
#> 2032-01-02 03:04:05.000006
print(m._secret_value)
#> 3

私有属性名称必须以下划线开头,以防止与模型字段发生冲突。但是,不支持双下名字(如 __attr__ )。

数据转换

Pydantic 可以对输入数据进行转换,以强制其符合模型字段类型,在某些情况下,这可能会导致信息丢失。例如:

from pydantic import BaseModel


class Model(BaseModel):
    a: int
    b: float
    c: str


print(Model(a=3.000, b='2.72', c=b'binary data').model_dump())
#> {'a': 3, 'b': 2.72, 'c': 'binary data'}

这是 Pydantic 的有意决策,也是最常用的方法。关于这个问题的更详细讨论,请参见此处。

然而,也支持严格的类型检查。

模型签名

所有 Pydantic 模型都将根据其字段生成签名:

import inspect

from pydantic import BaseModel, Field


class FooModel(BaseModel):
    id: int
    name: str = None
    description: str = 'Foo'
    apple: int = Field(alias='pear')


print(inspect.signature(FooModel))
#> (*, id: int, name: str = None, description: str = 'Foo', pear: int) -> None

准确的签名有助于自省目的,例如 FastAPIhypothesis 等库。

生成的签名也将尊重自定义的 __init__ 函数:

import inspect

from pydantic import BaseModel


class MyModel(BaseModel):
    id: int
    info: str = 'Foo'

    def __init__(self, id: int = 1, *, bar: str, **data) -> None:
        """My custom init!"""
        super().__init__(id=id, bar=bar, **data)


print(inspect.signature(MyModel))
#> (id: int = 1, *, bar: str, info: str = 'Foo') -> None

要包含在签名中,字段的别名或名称必须是有效的 Python 标识符。Pydantic 在生成签名时将优先考虑字段的别名,但如果别名不是有效的 Python 标识符,则可能使用字段名称。

如果字段的别名和名称都不是有效的标识符(通过使用 create_model 可能会出现这种情况),则将添加 **data 参数。此外,如果 model_config['extra'] == 'allow' ,则签名中始终会存在 **data 参数。

结构模式匹配

Pydantic 支持模型的结构模式匹配,正如 Python 3.10 中的 PEP 636 所介绍的那样。

from pydantic import BaseModel


class Pet(BaseModel):
    name: str
    species: str


a = Pet(name='Bones', species='dog')

match a:
    # match `species` to 'dog', declare and initialize `dog_name`
    case Pet(species='dog', name=dog_name):
        print(f'{dog_name} is a dog')
#> Bones is a dog
    # default case
    case _:
        print('No dog matched')

注意

匹配大小写的语句似乎创建了一个新模型,但不要被愚弄;它只是获取属性并进行比较或声明和初始化的语法糖。

属性副本

在许多情况下,传递给构造函数的参数将被复制,以便进行验证和必要的强制转换。

在这个例子中,请注意列表的 ID 在类构造后发生了变化,因为它在验证期间被复制了:

from typing import List

from pydantic import BaseModel


class C1:
    arr = []

    def __init__(self, in_arr):
        self.arr = in_arr


class C2(BaseModel):
    arr: List[int]


arr_orig = [1, 9, 10, 3]


c1 = C1(arr_orig)
c2 = C2(arr=arr_orig)
print('id(c1.arr) == id(c2.arr):', id(c1.arr) == id(c2.arr))
#> id(c1.arr) == id(c2.arr): False

注意

在某些情况下,Pydantic 不会复制属性,例如在传递模型时——我们使用模型本身。你可以通过设置 model_config['revalidate_instances'] = 'always' 来覆盖此行为。

额外字段

默认情况下,当你为不被识别的字段提供数据时,Pydantic 模型不会报错,它们只会被忽略:

from pydantic import BaseModel


class Model(BaseModel):
    x: int


m = Model(x=1, y='a')
assert m.model_dump() == {'x': 1}

如果你希望这引发一个错误,你可以通过 model_config 来实现这一点:

from pydantic import BaseModel, ConfigDict, ValidationError


class Model(BaseModel):
    x: int

    model_config = ConfigDict(extra='forbid')


try:
    Model(x=1, y='a')
except ValidationError as exc:
    print(exc)
    """
    1 validation error for Model
    y
      Extra inputs are not permitted [type=extra_forbidden, input_value='a', input_type=str]
    """

要保留提供的任何其他数据,可以设置 extra='allow' 。然后,其他字段将存储在 BaseModel.__pydantic_extra__ 中:

from pydantic import BaseModel, ConfigDict


class Model(BaseModel):
    x: int

    model_config = ConfigDict(extra='allow')


m = Model(x=1, y='a')
assert m.__pydantic_extra__ == {'y': 'a'}

默认情况下,不会对这些额外的项应用任何验证,但你可以通过覆盖 __pydantic_extra__ 的类型注释来为值设置类型:

from typing import Dict

from pydantic import BaseModel, ConfigDict, Field, ValidationError


class Model(BaseModel):
    __pydantic_extra__: Dict[str, int] = Field(init=False)  # (1)!

    x: int

    model_config = ConfigDict(extra='allow')


try:
    Model(x=1, y='a')
except ValidationError as exc:
    print(exc)
    """
    1 validation error for Model
    y
      Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str]
    """

m = Model(x=1, y='2')
assert m.x == 1
assert m.y == 2
assert m.model_dump() == {'x': 1, 'y': 2}
assert m.__pydantic_extra__ == {'y': 2}
  1. = Field(init=False) 在运行时没有任何效果,但可以防止 __pydantic_extra__ 字段被类型检查器视为模型的 __init__ 方法的参数。

相同的配置也适用于 TypedDictdataclass ,但配置是通过将类的 __pydantic_config__ 属性设置为有效的 ConfigDict 来控制的。


本文总阅读量