Dataclasses
如果您不想使用 Pydantic 的 BaseModel
,您可以改为在标准 dataclasses 上获得相同的数据验证。
from datetime import datetime
from typing import Optional
from pydantic.dataclasses import dataclass
@dataclass
class User:
id: int
name: str = 'John Doe'
signup_ts: Optional[datetime] = None
user = User(id='42', signup_ts='2032-06-21T12:00')
print(user)
"""
User(id=42, name='John Doe', signup_ts=datetime.datetime(2032, 6, 21, 12, 0))
"""
from datetime import datetime
from pydantic.dataclasses import dataclass
@dataclass
class User:
id: int
name: str = 'John Doe'
signup_ts: datetime | None = None
user = User(id='42', signup_ts='2032-06-21T12:00')
print(user)
"""
User(id=42, name='John Doe', signup_ts=datetime.datetime(2032, 6, 21, 12, 0))
"""
注意
请记住,Pydantic dataclasses 不是 Pydantic 模型的替代品。它们提供了与标准库 dataclasses 类似的功能,并增加了 Pydantic 验证。
在某些情况下,使用 Pydantic 模型进行子类化是更好的选择。
有关更多信息和讨论,请参阅 pydantic/pydantic#710。
Pydantic dataclasses 和模型之间的相似之处包括对以下内容的支持:
Pydantic dataclasses 和模型之间的一些差异包括:
与 Pydantic 模型类似,用于实例化 dataclass 的参数会被复制。
要使用各种方法来验证、转储和生成 JSON Schema,您可以将 dataclass 包装在 TypeAdapter
中,并使用其方法。
您可以同时使用 Pydantic 的 Field()
和标准库的 field()
函数
import dataclasses
from typing import Optional
from pydantic import Field, TypeAdapter
from pydantic.dataclasses import dataclass
@dataclass
class User:
id: int
name: str = 'John Doe'
friends: list[int] = dataclasses.field(default_factory=lambda: [0])
age: Optional[int] = dataclasses.field(
default=None,
metadata={'title': 'The age of the user', 'description': 'do not lie!'},
)
height: Optional[int] = Field(None, title='The height in cm', ge=50, le=300)
user = User(id='42')
print(TypeAdapter(User).json_schema())
"""
{
'properties': {
'id': {'title': 'Id', 'type': 'integer'},
'name': {'default': 'John Doe', 'title': 'Name', 'type': 'string'},
'friends': {
'items': {'type': 'integer'},
'title': 'Friends',
'type': 'array',
},
'age': {
'anyOf': [{'type': 'integer'}, {'type': 'null'}],
'default': None,
'description': 'do not lie!',
'title': 'The age of the user',
},
'height': {
'anyOf': [
{'maximum': 300, 'minimum': 50, 'type': 'integer'},
{'type': 'null'},
],
'default': None,
'title': 'The height in cm',
},
},
'required': ['id'],
'title': 'User',
'type': 'object',
}
"""
import dataclasses
from pydantic import Field, TypeAdapter
from pydantic.dataclasses import dataclass
@dataclass
class User:
id: int
name: str = 'John Doe'
friends: list[int] = dataclasses.field(default_factory=lambda: [0])
age: int | None = dataclasses.field(
default=None,
metadata={'title': 'The age of the user', 'description': 'do not lie!'},
)
height: int | None = Field(None, title='The height in cm', ge=50, le=300)
user = User(id='42')
print(TypeAdapter(User).json_schema())
"""
{
'properties': {
'id': {'title': 'Id', 'type': 'integer'},
'name': {'default': 'John Doe', 'title': 'Name', 'type': 'string'},
'friends': {
'items': {'type': 'integer'},
'title': 'Friends',
'type': 'array',
},
'age': {
'anyOf': [{'type': 'integer'}, {'type': 'null'}],
'default': None,
'description': 'do not lie!',
'title': 'The age of the user',
},
'height': {
'anyOf': [
{'maximum': 300, 'minimum': 50, 'type': 'integer'},
{'type': 'null'},
],
'default': None,
'title': 'The height in cm',
},
},
'required': ['id'],
'title': 'User',
'type': 'object',
}
"""
Pydantic @dataclass
装饰器接受与标准装饰器相同的参数,并额外接受一个 config
参数。
Dataclass 配置¶
如果您想像使用 BaseModel
一样修改配置,您有两种选择:
- 使用装饰器的
config
参数。 - 使用
__pydantic_config__
属性定义配置。
from pydantic import ConfigDict
from pydantic.dataclasses import dataclass
# Option 1 -- using the decorator argument:
@dataclass(config=ConfigDict(validate_assignment=True)) # (1)!
class MyDataclass1:
a: int
# Option 2 -- using an attribute:
@dataclass
class MyDataclass2:
a: int
__pydantic_config__ = ConfigDict(validate_assignment=True)
- 您可以在 API 参考中阅读更多关于
validate_assignment
的信息。
注意
虽然 Pydantic dataclasses 支持 extra
配置值,但标准库 dataclasses 的某些默认行为可能仍然适用。例如,在 extra
设置为 'allow'
的 Pydantic dataclass 上存在的任何额外字段都会在 dataclass 的字符串表示中被省略。也没有办法使用 __pydantic_extra__
属性提供验证。
重建 dataclass 模式¶
可以使用 rebuild_dataclass()
来重建 dataclass 的核心模式。有关更多详细信息,请参阅重建模型模式部分。
标准库 dataclasses 和 Pydantic dataclasses¶
继承自标准库 dataclasses¶
标准库 dataclasses(嵌套或非嵌套)也可以被继承,Pydantic 将自动验证所有继承的字段。
import dataclasses
import pydantic
@dataclasses.dataclass
class Z:
z: int
@dataclasses.dataclass
class Y(Z):
y: int = 0
@pydantic.dataclasses.dataclass
class X(Y):
x: int = 0
foo = X(x=b'1', y='2', z='3')
print(foo)
#> X(z=3, y=2, x=1)
try:
X(z='pika')
except pydantic.ValidationError as e:
print(e)
"""
1 validation error for X
z
Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='pika', input_type=str]
"""
与 BaseModel
一起使用标准库 dataclasses¶
当标准库 dataclass 在 Pydantic 模型、Pydantic dataclass 或 TypeAdapter
中使用时,将应用验证(并且配置保持不变)。这意味着使用标准库或 Pydantic dataclass 作为字段注解在功能上是等效的。
import dataclasses
from typing import Optional
from pydantic import BaseModel, ConfigDict, ValidationError
@dataclasses.dataclass(frozen=True)
class User:
name: str
class Foo(BaseModel):
# Required so that pydantic revalidates the model attributes:
model_config = ConfigDict(revalidate_instances='always')
user: Optional[User] = None
# nothing is validated as expected:
user = User(name=['not', 'a', 'string'])
print(user)
#> User(name=['not', 'a', 'string'])
try:
Foo(user=user)
except ValidationError as e:
print(e)
"""
1 validation error for Foo
user.name
Input should be a valid string [type=string_type, input_value=['not', 'a', 'string'], input_type=list]
"""
foo = Foo(user=User(name='pika'))
try:
foo.user.name = 'bulbi'
except dataclasses.FrozenInstanceError as e:
print(e)
#> cannot assign to field 'name'
import dataclasses
from pydantic import BaseModel, ConfigDict, ValidationError
@dataclasses.dataclass(frozen=True)
class User:
name: str
class Foo(BaseModel):
# Required so that pydantic revalidates the model attributes:
model_config = ConfigDict(revalidate_instances='always')
user: User | None = None
# nothing is validated as expected:
user = User(name=['not', 'a', 'string'])
print(user)
#> User(name=['not', 'a', 'string'])
try:
Foo(user=user)
except ValidationError as e:
print(e)
"""
1 validation error for Foo
user.name
Input should be a valid string [type=string_type, input_value=['not', 'a', 'string'], input_type=list]
"""
foo = Foo(user=User(name='pika'))
try:
foo.user.name = 'bulbi'
except dataclasses.FrozenInstanceError as e:
print(e)
#> cannot assign to field 'name'
使用自定义类型¶
如上所述,验证应用于标准库 dataclasses。如果您使用自定义类型,则在尝试引用 dataclass 时会收到错误。要规避此问题,您可以在 dataclass 上设置 arbitrary_types_allowed
配置值
import dataclasses
from pydantic import BaseModel, ConfigDict
from pydantic.errors import PydanticSchemaGenerationError
class ArbitraryType:
def __init__(self, value):
self.value = value
def __repr__(self):
return f'ArbitraryType(value={self.value!r})'
@dataclasses.dataclass
class DC:
a: ArbitraryType
b: str
# valid as it is a stdlib dataclass without validation:
my_dc = DC(a=ArbitraryType(value=3), b='qwe')
try:
class Model(BaseModel):
dc: DC
other: str
# invalid as dc is now validated with pydantic, and ArbitraryType is not a known type
Model(dc=my_dc, other='other')
except PydanticSchemaGenerationError as e:
print(e.message)
"""
Unable to generate pydantic-core schema for <class '__main__.ArbitraryType'>. Set `arbitrary_types_allowed=True` in the model_config to ignore this error or implement `__get_pydantic_core_schema__` on your type to fully support it.
If you got this error by calling handler(<some type>) within `__get_pydantic_core_schema__` then you likely need to call `handler.generate_schema(<some type>)` since we do not call `__get_pydantic_core_schema__` on `<some type>` otherwise to avoid infinite recursion.
"""
# valid as we set arbitrary_types_allowed=True, and that config pushes down to the nested vanilla dataclass
class Model(BaseModel):
model_config = ConfigDict(arbitrary_types_allowed=True)
dc: DC
other: str
m = Model(dc=my_dc, other='other')
print(repr(m))
#> Model(dc=DC(a=ArbitraryType(value=3), b='qwe'), other='other')
检查 dataclass 是否为 Pydantic dataclass¶
Pydantic dataclasses 仍然被认为是 dataclasses,因此使用 dataclasses.is_dataclass
将返回 True
。要检查类型是否专门为 pydantic dataclass,您可以使用 is_pydantic_dataclass
函数。
import dataclasses
import pydantic
@dataclasses.dataclass
class StdLibDataclass:
id: int
PydanticDataclass = pydantic.dataclasses.dataclass(StdLibDataclass)
print(dataclasses.is_dataclass(StdLibDataclass))
#> True
print(pydantic.dataclasses.is_pydantic_dataclass(StdLibDataclass))
#> False
print(dataclasses.is_dataclass(PydanticDataclass))
#> True
print(pydantic.dataclasses.is_pydantic_dataclass(PydanticDataclass))
#> True
验证器和初始化钩子¶
验证器也适用于 Pydantic dataclasses
from pydantic import field_validator
from pydantic.dataclasses import dataclass
@dataclass
class DemoDataclass:
product_id: str # should be a five-digit string, may have leading zeros
@field_validator('product_id', mode='before')
@classmethod
def convert_int_serial(cls, v):
if isinstance(v, int):
v = str(v).zfill(5)
return v
print(DemoDataclass(product_id='01234'))
#> DemoDataclass(product_id='01234')
print(DemoDataclass(product_id=2468))
#> DemoDataclass(product_id='02468')
dataclass __post_init__()
方法也受支持,并且将在调用before和after模型验证器之间调用。
示例
from pydantic_core import ArgsKwargs
from typing_extensions import Self
from pydantic import model_validator
from pydantic.dataclasses import dataclass
@dataclass
class Birth:
year: int
month: int
day: int
@dataclass
class User:
birth: Birth
@model_validator(mode='before')
@classmethod
def before(cls, values: ArgsKwargs) -> ArgsKwargs:
print(f'First: {values}') # (1)!
"""
First: ArgsKwargs((), {'birth': {'year': 1995, 'month': 3, 'day': 2}})
"""
return values
@model_validator(mode='after')
def after(self) -> Self:
print(f'Third: {self}')
#> Third: User(birth=Birth(year=1995, month=3, day=2))
return self
def __post_init__(self):
print(f'Second: {self.birth}')
#> Second: Birth(year=1995, month=3, day=2)
user = User(**{'birth': {'year': 1995, 'month': 3, 'day': 2}})
- 与 Pydantic 模型不同,
values
参数的类型为ArgsKwargs