模型
API 文档
在 Pydantic 中定义模式的主要方法之一是通过模型。模型只是继承自 BaseModel
并将字段定义为注解属性的类。
您可以将模型视为类似于 C 语言中的结构,或者 API 中单个端点的要求。
模型与 Python 的 数据类 有许多相似之处,但在设计时考虑了一些细微但重要的差异,这些差异简化了与验证、序列化和 JSON 模式生成相关的某些工作流程。您可以在文档的 数据类 部分找到更多讨论。
不可信的数据可以传递给模型,经过解析和验证后,Pydantic 保证生成的模型实例的字段将符合模型上定义的字段类型。
验证 — 一种故意的用词不当
TL;DR
我们使用术语“验证”来指代实例化符合指定类型和约束的模型(或其他类型)的过程。这项任务是 Pydantic 闻名于世的,在口语中被最广泛地认为是“验证”,即使在其他上下文中,“验证”一词可能更具限制性。
长版本
围绕术语“验证”的潜在混淆源于一个事实,即严格来说,Pydantic 的主要重点与“验证”的字典定义并不完全一致
验证
名词 检查或证明某事的有效性或准确性的行为。
在 Pydantic 中,术语“验证”指的是实例化符合指定类型和约束的模型(或其他类型)的过程。Pydantic 保证输出的类型和约束,而不是输入数据。当考虑到当数据无法成功解析为模型实例时会引发 Pydantic 的 ValidationError
时,这种区别变得显而易见。
虽然这种区别最初看起来可能很微妙,但它具有实际意义。在某些情况下,“验证”不仅仅是模型创建,还可以包括数据的复制和强制转换。这可能涉及复制传递给构造函数的参数,以便在不改变原始输入数据的情况下执行强制转换为新类型。为了更深入地理解对您的用法的含义,请参阅下面的 数据转换 和 属性副本 部分。
本质上,Pydantic 的主要目标是确保后处理(称为“验证”)的结果结构精确地符合应用的类型提示。鉴于“验证”作为此过程的口语术语已被广泛采用,我们将在文档中始终如一地使用它。
虽然术语“解析”和“验证”以前可以互换使用,但展望未来,我们旨在专门使用“验证”,而“解析”则专门用于讨论与 JSON 解析 相关的内容。
基本模型用法¶
from pydantic import BaseModel, ConfigDict
class User(BaseModel):
id: int
name: str = 'Jane Doe'
model_config = ConfigDict(str_max_length=10) # (1)!
在此示例中,User
是一个具有两个字段的模型
id
,它是一个整数并且是必需的name
,它是一个字符串并且不是必需的(它有一个默认值)。
可以使用 Field()
函数以多种方式自定义字段。有关更多信息,请参阅 关于字段的文档。
然后可以实例化模型
user = User(id='123')
user
是 User
的一个实例。对象的初始化将执行所有解析和验证。如果没有引发 ValidationError
异常,您就知道生成的模型实例是有效的。
模型的字段可以作为 user
对象的普通属性访问
assert user.name == 'Jane Doe' # (1)!
assert user.id == 123 # (2)!
assert isinstance(user.id, int)
- 初始化
user
时未设置name
,因此使用了默认值。可以检查model_fields_set
属性以检查在实例化期间显式设置的字段名称。 - 请注意,字符串
'123'
被强制转换为整数,其值为123
。有关 Pydantic 强制逻辑的更多详细信息,请参阅 数据转换 部分。
可以使用 model_dump()
方法序列化模型实例
assert user.model_dump() == {'id': 123, 'name': 'Jane Doe'}
在实例上调用 dict 也将提供一个字典,但嵌套字段不会递归转换为字典。model_dump()
还提供了许多参数来自定义序列化结果。
默认情况下,模型是可变的,并且可以通过属性赋值更改字段值
user.id = 321
assert user.id == 321
警告
在定义模型时,请注意字段名称及其类型注解之间的命名冲突。
例如,以下内容将无法按预期运行,并且会产生验证错误
from typing import Optional
from pydantic import BaseModel
class Boo(BaseModel):
int: Optional[int] = None
m = Boo(int=123) # Will fail to validate.
由于 Python 评估 注解赋值语句 的方式,该语句等效于 int: None = None
,因此导致验证错误。
模型方法和属性¶
上面的示例仅显示了模型功能的冰山一角。模型具有以下方法和属性
model_validate()
:根据 Pydantic 模型验证给定对象。请参阅 验证数据。model_validate_json()
:根据 Pydantic 模型验证给定的 JSON 数据。请参阅 验证数据。model_construct()
:创建模型而不运行验证。请参阅 创建没有验证的模型。model_dump()
:返回模型字段和值的字典。请参阅 序列化。model_dump_json()
:返回model_dump()
的 JSON 字符串表示形式。请参阅 序列化。model_copy()
:返回模型副本(默认情况下为浅拷贝)。请参阅 序列化。model_json_schema()
:返回表示模型 JSON 模式的可 JSON 化的字典。请参阅 JSON 模式。model_fields
:字段名称及其定义(FieldInfo
实例)之间的映射。model_computed_fields
:计算字段名称及其定义(ComputedFieldInfo
实例)之间的映射。model_extra
:在验证期间设置的额外字段。model_fields_set
:在模型初始化时显式提供的字段集。model_parametrized_name()
:计算泛型类的参数化的类名。model_post_init()
:在模型实例化并且应用所有字段验证器后执行其他操作。model_rebuild()
:重建模型模式,这也支持构建递归泛型模型。请参阅 重建模型模式。
注意
有关类定义(包括方法和属性的完整列表),请参阅 BaseModel
的 API 文档。
提示
有关 Pydantic V1 更改的详细信息,请参阅 迁移指南 中的 pydantic.BaseModel
的更改。
数据转换¶
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 提供了 严格模式,其中不执行数据转换。值必须与声明的字段类型相同。
集合也是如此。在大多数情况下,您不应使用抽象容器类,而只需使用具体类型,例如 list
from pydantic import BaseModel
class Model(BaseModel):
items: list[int] # (1)!
print(Model(items=(1, 2, 3)))
#> items=[1, 2, 3]
- 在这种情况下,您可能很想使用抽象
Sequence
类型来同时允许列表和元组。但是 Pydantic 负责将元组输入转换为列表,因此在大多数情况下,这是不必要的。
此外,使用这些抽象类型也可能导致 验证性能不佳,并且通常使用具体容器类型将避免不必要的检查。
额外数据¶
默认情况下,当您提供额外数据时,Pydantic 模型不会报错,这些值将被简单地忽略
from pydantic import BaseModel
class Model(BaseModel):
x: int
m = Model(x=1, y='a')
assert m.model_dump() == {'x': 1}
可以使用 extra
配置值来控制此行为
from pydantic import BaseModel, ConfigDict
class Model(BaseModel):
x: int
model_config = ConfigDict(extra='allow')
m = Model(x=1, y='a') # (1)!
assert m.model_dump() == {'x': 1, 'y': 'a'}
assert m.__pydantic_extra__ == {'y': 'a'}
- 如果
extra
设置为'forbid'
,则会失败。
配置可以采用三个值
'ignore'
:提供额外数据将被忽略(默认值)。'forbid'
:不允许提供额外数据。'allow'
:允许提供额外数据并存储在__pydantic_extra__
字典属性中。__pydantic_extra__
可以显式注解以提供额外字段的验证。
有关更多详细信息,请参阅 extra
API 文档。
Pydantic 数据类也支持额外数据(请参阅 数据类配置 部分)。
嵌套模型¶
可以使用模型本身作为注解中的类型来定义更复杂的层次数据结构。
from typing import 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'}],
}
"""
from pydantic import BaseModel
class Foo(BaseModel):
count: int
size: float | None = 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'}],
}
"""
支持自引用模型。有关更多详细信息,请参阅与 前向注解 相关的文档。
重建模型模式¶
当您在代码中定义模型类时,Pydantic 将分析类的正文以收集执行验证和序列化所需的各种信息,这些信息收集在核心模式中。值得注意的是,模型的类型注解被评估以了解每个字段的有效类型(更多信息可以在 架构 文档中找到)。但是,注解可能引用在创建模型类时未定义的符号。为了规避此问题,可以使用 model_rebuild()
方法
from pydantic import BaseModel, PydanticUserError
class Foo(BaseModel):
x: 'Bar' # (1)!
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',
}
"""
- 当创建
Foo
类时,Bar
尚未定义。因此,正在使用 前向注解。
Pydantic 尝试自动确定何时需要这样做,并在未完成时报错,但当处理递归模型或泛型时,您可能需要主动调用 model_rebuild()
。
在 V2 中,model_rebuild()
替换了 V1 中的 update_forward_refs()
。新行为有一些细微的差异。最大的变化是,当在最外层模型上调用 model_rebuild()
时,它会构建一个用于验证整个模型(嵌套模型和所有模型)的核心模式,因此在调用 model_rebuild()
之前,所有级别的类型都需要准备就绪。
任意类实例¶
(以前称为“ORM 模式”/from_orm
)。
Pydantic 模型也可以通过读取与模型字段名称对应的实例属性从任意类实例创建。此功能的一个常见应用是与对象关系映射 (ORM) 集成。
为此,请将 from_attributes
配置值设置为 True
(有关更多详细信息,请参阅 配置 文档)。
此示例使用 SQLAlchemy,但相同的方法应适用于任何 ORM。
from typing import Annotated
from sqlalchemy import ARRAY, String
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from pydantic import BaseModel, ConfigDict, StringConstraints
class Base(DeclarativeBase):
pass
class CompanyOrm(Base):
__tablename__ = 'companies'
id: Mapped[int] = mapped_column(primary_key=True, nullable=False)
public_key: Mapped[str] = mapped_column(
String(20), index=True, nullable=False, unique=True
)
domains: Mapped[list[str]] = mapped_column(ARRAY(String(255)))
class CompanyModel(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
public_key: Annotated[str, StringConstraints(max_length=20)]
domains: list[Annotated[str, StringConstraints(max_length=255)]]
co_orm = CompanyOrm(
id=123,
public_key='foobar',
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' domains=['example.com', 'foobar.com']
嵌套属性¶
当使用属性解析模型时,将从顶层属性和更深层嵌套的属性创建模型实例,具体取决于情况。
这是一个演示该原理的示例
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
异常。
无论发现多少错误,都只会引发一个异常,并且该验证错误将包含有关所有错误及其发生方式的信息。
有关标准错误和自定义错误的详细信息,请参阅 错误处理。
作为演示
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 在模型类上提供了三种用于解析数据的方法
model_validate()
:这与模型的__init__
方法非常相似,不同之处在于它接受字典或对象,而不是关键字参数。如果传递的对象无法验证,或者它不是字典或相关模型的实例,则会引发ValidationError
。model_validate_json()
:这会将提供的数据验证为 JSON 字符串或bytes
对象。如果您的传入数据是 JSON 有效负载,则通常认为这更快(而不是手动将数据解析为字典)。在文档的 JSON 部分中了解有关 JSON 解析的更多信息。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]
"""
from datetime import datetime
from pydantic import BaseModel, ValidationError
class User(BaseModel):
id: int
name: str = 'John Doe'
signup_ts: datetime | None = 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
。
注意
如果您要将模型实例传递给 model_validate
,您将需要考虑在模型的配置中设置 revalidate_instances
。如果您不设置此值,则将在模型实例上跳过验证。请参阅以下示例
from pydantic import BaseModel
class Model(BaseModel):
a: int
m = Model(a=0)
# note: setting `validate_assignment` to `True` in the config 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)
from pydantic import BaseModel, ConfigDict, ValidationError
class Model(BaseModel):
a: int
model_config = ConfigDict(revalidate_instances='always')
m = Model(a=0)
# note: setting `validate_assignment` to `True` in the config 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()
方法,该方法允许创建没有验证的模型。这在至少以下几种情况下可能很有用
- 当处理已知有效 (出于性能原因) 的复杂数据时
- 当一个或多个验证器函数是非幂等的时
- 当一个或多个验证器函数具有您不想触发的副作用时。
警告
model_construct()
不执行任何验证,这意味着它可以创建无效的模型。您应该仅将 model_construct()
方法与已验证或您绝对信任的数据一起使用。
注意
在 Pydantic V2 中,验证(通过直接实例化或 model_validate*
方法)和 model_construct()
之间的性能差距已大大缩小。对于简单模型,进行验证甚至可能更快。如果您出于性能原因使用 model_construct()
,您可能需要在假设它实际上更快之前分析您的用例。
请注意,对于 根模型,根值可以位置方式传递给 model_construct()
,而不是使用关键字参数。
以下是关于 model_construct()
行为的一些其他说明
- 当我们说“不执行验证”时 — 这包括将字典转换为模型实例。因此,如果您有一个引用模型类型的字段,您将需要自己将内部字典转换为模型。
- 如果您没有为具有默认值的字段传递关键字参数,则仍将使用默认值。
- 对于具有私有属性的模型,
__pydantic_private__
字典将以与使用验证创建模型时相同的方式填充。 - 不会调用模型或其任何父类的
__init__
方法,即使定义了自定义__init__
方法也是如此。
关于 额外数据 与 model_construct()
的行为
- 对于
extra
设置为'allow'
的模型,不对应于字段的数据将正确存储在__pydantic_extra__
字典中并保存到模型的__dict__
属性。 - 对于
extra
设置为'ignore'
的模型,不对应于字段的数据将被忽略 — 也就是说,不存储在实例上的__pydantic_extra__
或__dict__
中。 - 与使用验证实例化模型不同,当
model_construct()
的extra
设置为'forbid'
时,如果存在不对应于字段的数据,则不会引发错误。相反,所述输入数据将被简单地忽略。
泛型模型¶
Pydantic 支持创建泛型模型,以更轻松地重用通用模型结构。新的 类型参数语法(由 Python 3.12 中的 PEP 695 引入)和旧语法均受支持(有关更多详细信息,请参阅 Python 文档)。
这是一个使用泛型 Pydantic 模型创建易于重用的 HTTP 响应有效负载包装器的示例
from typing import Generic, TypeVar
from pydantic import BaseModel, ValidationError
DataT = TypeVar('DataT') # (1)!
class DataModel(BaseModel):
number: int
class Response(BaseModel, Generic[DataT]): # (2)!
data: DataT # (3)!
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(number=1)
print(Response[DataModel](data=data).model_dump())
#> {'data': {'number': 1}}
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]
"""
- 声明一个或多个 类型变量,用于参数化您的模型。
- 声明一个从
BaseModel
和typing.Generic
继承的 Pydantic 模型(按此特定顺序),并将您先前声明的类型变量列表添加为Generic
父级的参数。 - 在您希望将类型变量替换为其他类型的位置使用类型变量作为注解。
from pydantic import BaseModel, ValidationError
class DataModel(BaseModel):
number: int
class Response[DataT](BaseModel): # (1)!
data: DataT # (2)!
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(number=1)
print(Response[DataModel](data=data).model_dump())
#> {'data': {'number': 1}}
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]
"""
- 声明一个 Pydantic 模型,并将类型变量列表添加为类型参数。
- 在您希望将类型变量替换为其他类型的位置使用类型变量作为注解。
警告
当使用具体类型参数化模型时,如果提供的类型具有上限,Pydantic 不验证提供的类型是否 可分配给类型变量。
在泛型模型上设置的任何 配置、验证 或 序列化 逻辑也将应用于参数化类,就像从模型类继承时一样。任何自定义方法或属性也将被继承。
泛型模型也与类型检查器正确集成,因此您可以获得所有类型检查,如果您为每个参数化声明不同的类型,您将期望获得这些类型检查。
注意
在内部,当泛型模型类被参数化时,Pydantic 会在运行时创建泛型模型的子类。这些类被缓存,因此使用泛型模型应该引入的开销最小。
要从泛型模型继承并保留它是泛型的事实,子类也必须从 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]):
pass
# Parametrize `TypeX` with `int`:
print(ChildClass[int](X=1))
#> X=1
您还可以创建一个泛型子类,该子类部分或完全替换超类中的类型变量
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
# Parametrize `TypeY` with `str`:
print(ChildClass[str, int](x='1', y='y', z='3'))
#> x=1 y='y' z=3
如果具体子类的名称很重要,您还可以通过覆盖 model_parametrized_name()
方法来覆盖默认名称生成
from typing import Any, Generic, 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)))
"""
在嵌套模型中使用相同的类型变量允许您在模型中的不同点强制执行类型关系
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:
print(OuterT[int](outer='a', nested=InnerT(inner='a'))) # (1)!
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.inner
Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str]
"""
OuterT
模型使用int
参数化,但在验证期间与T
注解关联的数据类型为str
,从而导致验证错误。
警告
虽然它可能不会引发错误,但我们强烈建议不要在 isinstance()
检查中使用参数化泛型。
例如,您不应执行 isinstance(my_model, MyGenericModel[int])
。但是,可以执行 isinstance(my_model, MyGenericModel)
(请注意,对于标准泛型,使用参数化泛型类执行子类检查将引发错误)。
如果您需要对参数化泛型执行 isinstance()
检查,您可以通过子类化参数化泛型类来执行此操作
class MyIntModel(MyGenericModel[int]): ...
isinstance(my_model, MyIntModel)
实现细节
当使用嵌套泛型模型时,Pydantic 有时会执行重新验证,以尝试产生最直观的验证结果。具体来说,如果您有一个类型为 GenericModel[SomeType]
的字段,并且您针对此字段验证类似于 GenericModel[SomeCompatibleType]
的数据,我们将检查数据,识别出输入数据有点像 GenericModel
的“松散”子类,并重新验证包含的 SomeCompatibleType
数据。
这增加了一些验证开销,但对于下面显示的案例,使事情更加直观。
from typing import Any, Generic, TypeVar
from pydantic import BaseModel
T = TypeVar('T')
class GenericModel(BaseModel, Generic[T]):
a: T
class Model(BaseModel):
inner: GenericModel[Any]
print(repr(Model.model_validate(Model(inner=GenericModel[int](a=1)))))
#> Model(inner=GenericModel[Any](a=1))
请注意,如果您例如针对 GenericModel[int]
进行验证并传入实例 GenericModel[str](a='not an int')
,则验证仍然会失败。
还值得注意的是,此模式也将重新触发任何自定义验证,例如额外的模型验证器等。验证器将在第一遍调用一次,直接针对 GenericModel[Any]
进行验证。该验证失败,因为 GenericModel[int]
不是 GenericModel[Any]
的子类。这与上面关于在 isinstance()
和 issubclass()
检查中使用参数化泛型的复杂性的警告有关。然后,验证器将在第二遍(在更宽松的强制重新验证阶段)再次调用,这将成功。为了更好地理解这种后果,请参阅下文
from typing import Any, Generic, Self, TypeVar
from pydantic import BaseModel, model_validator
T = TypeVar('T')
class GenericModel(BaseModel, Generic[T]):
a: T
@model_validator(mode='after')
def validate_after(self: Self) -> Self:
print('after validator running custom validation...')
return self
class Model(BaseModel):
inner: GenericModel[Any]
m = Model.model_validate(Model(inner=GenericModel[int](a=1)))
#> after validator running custom validation...
#> after validator running custom validation...
print(repr(m))
#> Model(inner=GenericModel[Any](a=1))
未参数化类型变量的验证¶
当将类型变量保持未参数化时,Pydantic 处理泛型模型的方式类似于处理内置泛型类型(如 list
和 dict
)的方式
from typing import Generic
from typing_extensions import TypeVar
from pydantic import BaseModel, ValidationError
T = TypeVar('T')
U = TypeVar('U', bound=int)
V = TypeVar('V', default=str)
class Model(BaseModel, Generic[T, U, V]):
t: T
u: U
v: V
print(Model(t='t', u=1, v='v'))
#> t='t' u=1 v='v'
try:
Model(t='t', u='u', v=1)
except ValidationError as exc:
print(exc)
"""
2 validation errors for Model
u
Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='u', input_type=str]
v
Input should be a valid string [type=string_type, input_value=1, input_type=int]
"""
警告
在某些情况下,针对未参数化的泛型模型进行验证可能会导致数据丢失。具体而言,如果正在使用类型变量上限、约束或默认值的子类型,并且模型未显式参数化,则生成的类型将不会是所提供的类型
from typing import Generic, TypeVar
from pydantic import BaseModel
ItemT = TypeVar('ItemT', bound='ItemBase')
class ItemBase(BaseModel): ...
class IntItem(ItemBase):
value: int
class ItemHolder(BaseModel, Generic[ItemT]):
item: ItemT
loaded_data = {'item': {'value': 1}}
print(ItemHolder(**loaded_data)) # (1)!
#> item=ItemBase()
print(ItemHolder[IntItem](**loaded_data)) # (2)!
#> item=IntItem(value=1)
- 当泛型未参数化时,输入数据将针对
ItemT
上限进行验证。 鉴于ItemBase
没有字段,item
字段信息将丢失。 - 在这种情况下,类型变量被显式参数化,因此输入数据将针对
IntItem
类进行验证。
未参数化类型变量的序列化¶
当使用带有上限、约束或默认值的类型变量时,序列化的行为有所不同
如果 Pydantic 模型在类型变量上限中使用,并且类型变量从未参数化,则 Pydantic 将使用上限进行验证,但在序列化方面将值视为 Any
from typing import Generic, TypeVar
from pydantic import BaseModel
class ErrorDetails(BaseModel):
foo: str
ErrorDataT = TypeVar('ErrorDataT', bound=ErrorDetails)
class Error(BaseModel, Generic[ErrorDataT]):
message: str
details: 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, 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}}
但是,如果正在使用约束或默认值(根据 PEP 696),则如果类型变量未参数化,默认类型或约束将用于验证和序列化。 您可以使用 SerializeAsAny
来覆盖此行为
from typing import Generic
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: 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',
},
}
# If `ErrorDataT` was using an upper bound, `bar` would be present in `details`.
class SerializeAsAnyError(BaseModel, Generic[ErrorDataT]):
message: str
details: 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 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: 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',
},
}
# If `ErrorDataT` was using an upper bound, `bar` would be present in `details`.
class SerializeAsAnyError(BaseModel, Generic[ErrorDataT]):
message: str
details: 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',
},
}
动态模型创建¶
API 文档
在某些情况下,需要使用运行时信息来指定字段以创建模型。 Pydantic 提供了 create_model()
函数,以允许动态创建模型
from pydantic import BaseModel, create_model
DynamicFoobarModel = create_model('DynamicFoobarModel', foo=str, bar=(int, 123))
# Equivalent to:
class StaticFoobarModel(BaseModel):
foo: str
bar: int = 123
字段定义被指定为关键字参数,并且应该是以下之一
- 单个元素,表示字段的类型注解。
- 一个二元组,第一个元素是类型,第二个元素是赋值(可以是默认值或
Field()
函数)。
这是一个更高级的示例
from typing import Annotated
from pydantic import BaseModel, Field, PrivateAttr, create_model
DynamicModel = create_model(
'DynamicModel',
foo=(str, Field(alias='FOO')),
bar=Annotated[str, Field(description='Bar field')],
_private=(int, PrivateAttr(default=1)),
)
class StaticModel(BaseModel):
foo: str = Field(alias='FOO')
bar: Annotated[str, Field(description='Bar field')]
_private: int = PrivateAttr(default=1)
特殊的关键字参数 __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 alphanum(cls, v):
assert v.isalnum(), 'must be alphanumeric'
return v
validators = {
'username_validator': field_validator('username')(alphanum) # (1)!
}
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]
"""
- 请确保验证器名称不与任何字段名称冲突,因为在内部,Pydantic 会将所有成员收集到一个命名空间中,并使用
types
模块实用程序 模仿类的正常创建。
注意
要 pickle 动态创建的模型
- 模型必须全局定义
- 必须提供
__module__
参数
RootModel
和自定义根类型¶
Pydantic 模型可以通过子类化 pydantic.RootModel
来定义“自定义根类型”。
根类型可以是 Pydantic 支持的任何类型,并通过 RootModel
的泛型参数指定。 根值可以通过第一个也是唯一的参数传递给模型的 __init__
或 model_validate
。
这是一个关于其工作原理的示例
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 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 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
字段顺序¶
字段顺序以下列方式影响模型
- 字段顺序保留在模型 JSON Schema 中
- 字段顺序保留在验证错误中
- 字段顺序由
.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',)]
自动排除的属性¶
类变量¶
使用 ClassVar
注解的属性被 Pydantic 正确地视为类变量,并且不会成为模型实例上的字段
from typing import ClassVar
from pydantic import BaseModel
class Model(BaseModel):
x: ClassVar[int] = 1
y: int = 2
m = Model()
print(m)
#> y=2
print(Model.x)
#> 1
私有模型属性¶
API 文档
名称带有前导下划线的属性不会被 Pydantic 视为字段,并且不会包含在模型模式中。 相反,这些属性被转换为“私有属性”,在调用 __init__
、model_validate
等期间不会对其进行验证甚至设置。
这是一个用法示例
from datetime import datetime
from random import randint
from typing import Any
from pydantic import BaseModel, PrivateAttr
class TimeAwareModel(BaseModel):
_processed_at: datetime = PrivateAttr(default_factory=datetime.now)
_secret_value: str
def model_post_init(self, context: Any) -> None:
# 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 模型都将根据其字段生成签名
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
准确的签名对于内省目的和 FastAPI
或 hypothesis
等库非常有用。
生成的签名也将尊重自定义的 __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')
注意
match-case 语句可能看起来像是创建了一个新模型,但不要被愚弄; 它只是获取属性并将其与另一个属性进行比较或声明和初始化的语法糖。
属性副本¶
在许多情况下,传递给构造函数的参数将被复制,以便执行验证并在必要时进行强制转换。
在此示例中,请注意列表的 ID 在类构造后会发生更改,因为它在验证期间已被复制
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(f'{id(c1.arr) == id(c2.arr)=}')
#> id(c1.arr) == id(c2.arr)=False
注意
在某些情况下,Pydantic 不会复制属性,例如在传递模型时 — 我们按原样使用模型。 您可以通过设置 model_config['revalidate_instances'] = 'always'
来覆盖此行为。