Pydantic 数据模型:定义、验证和解析一步到位

Pydantic 数据模型:定义、验证和解析一步到位

在现代软件开发中,数据验证和解析是至关重要的环节。无论是处理用户输入、API 请求,还是数据库交互,我们都需要确保数据的有效性和一致性。传统的手动数据验证方法不仅繁琐易错,而且难以维护。幸运的是,Python 生态系统中涌现出许多优秀的库来简化这一过程,其中 Pydantic 以其卓越的性能、类型注解支持和易用性脱颖而出,成为数据验证和解析领域的佼佼者。

本文将深入探讨 Pydantic,详细介绍其核心概念、使用方法和高级特性,帮助您全面掌握这一强大的工具,并将其应用到您的项目中,构建更健壮、更可靠的应用程序。

1. Pydantic 简介:不仅仅是数据验证

Pydantic 是一个基于 Python 类型注解的数据验证和解析库。它允许您使用 Python 类型提示来定义数据模型,并自动执行数据验证、类型转换和文档生成。Pydantic 的核心优势在于:

  • 类型驱动: Pydantic 充分利用 Python 类型注解 (Type Hints) 的优势。您只需使用类型注解定义数据结构,Pydantic 就会自动处理数据验证和转换。这不仅简化了代码,还提高了代码的可读性和可维护性。
  • 性能卓越: Pydantic 的底层使用了 Cython 进行优化,因此在性能方面表现出色,尤其是在处理大量数据时。它比其他类似的库(如 Marshmallow)快得多。
  • 易于使用: Pydantic 的 API 设计简洁直观。您无需学习复杂的 DSL 或配置,只需使用 Python 类型注解即可定义数据模型。
  • 自动文档生成: Pydantic 可以根据您定义的数据模型自动生成 JSON Schema 和 OpenAPI/Swagger 文档,方便 API 的开发和维护。
  • 强大的错误处理: Pydantic 提供清晰、详细的错误消息,帮助您快速定位和修复数据验证问题。
  • 可扩展性: Pydantic 提供了丰富的自定义选项,允许您根据需要扩展其功能,例如自定义验证器、类型转换器等。
  • 广泛的应用场景: Pydantic 不仅适用于 Web 开发(例如 FastAPI 框架就深度集成了 Pydantic),还可用于数据科学、机器学习、配置文件处理等各种场景。

2. 定义 Pydantic 模型:类型注解的力量

Pydantic 的核心是数据模型(Model)。您可以通过继承 pydantic.BaseModel 类并使用 Python 类型注解来定义数据模型。让我们从一个简单的例子开始:

```python
from pydantic import BaseModel

class User(BaseModel):
id: int
name: str
signup_ts: datetime | None = None
friends: list[int] = []
```

在这个例子中,我们定义了一个名为 User 的 Pydantic 模型。它具有以下字段:

  • id: 整数类型,必填字段。
  • name: 字符串类型,必填字段。
  • signup_ts: datetime 类型或 None,可选字段,默认值为 None
  • friends: 整数列表,可选字段,默认值为空列表。

通过使用类型注解,我们清晰地定义了每个字段的类型和约束。Pydantic 将自动根据这些类型注解进行数据验证和转换。

2.1 常用数据类型

Pydantic 支持多种内置数据类型,包括:

  • 基本类型: int, float, str, bool, bytes, datetime, date, time, timedelta
  • 集合类型: list, tuple, set, frozenset, dict
  • 特殊类型: Any, Union, Optional, Literal, UUID, FilePath, DirectoryPath, EmailStr, UrlStr 等。

您还可以使用嵌套模型来定义复杂的数据结构:

```python
from pydantic import BaseModel

class Address(BaseModel):
street: str
city: str
zip_code: str

class User(BaseModel):
id: int
name: str
address: Address
```

在这个例子中,User 模型包含一个 address 字段,其类型为另一个 Pydantic 模型 Address

2.2 字段配置:更精细的控制

除了类型注解,Pydantic 还允许您使用 Field 类对字段进行更精细的配置。Field 类提供了许多参数,用于自定义字段的行为:

```python
from pydantic import BaseModel, Field

class User(BaseModel):
id: int = Field(..., gt=0) # 必须大于 0
name: str = Field(..., min_length=3, max_length=50) # 长度限制
email: str = Field(..., regex=r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$") # 正则表达式
age: int | None = Field(None, ge=18, le=120) # 可选,年龄范围 18-120
description: str | None = Field(None, alias="desc") #别名
password: str = Field(..., exclude=True) #从模型导出中排除

```

Field 类的常用参数包括:

  • default: 字段的默认值。
  • default_factory: 一个 callable 对象, 用于生成默认值. 与 default 互斥.
  • alias: 字段的别名,用于在解析数据时使用不同的名称。
  • title: 字段的标题,用于生成文档。
  • description: 字段的描述,用于生成文档。
  • const: 字段必须等于该值.
  • gt: 字段必须大于该值 (greater than)。
  • ge: 字段必须大于或等于该值 (greater than or equal to)。
  • lt: 字段必须小于该值 (less than)。
  • le: 字段必须小于或等于该值 (less than or equal to)。
  • min_length: 字符串或列表的最小长度。
  • max_length: 字符串或列表的最大长度。
  • regex: 字符串必须匹配的正则表达式。
  • exclude: 在模型导出 (如 dict(), json()) 时是否排除该字段。

2.3 自定义验证器:扩展 Pydantic 的功能

Pydantic 提供了两种类型的自定义验证器:

  • @validator 装饰器: 用于定义字段级别的验证器。
  • @root_validator 装饰器: 用于定义模型级别的验证器。

2.3.1 @validator:字段级验证

@validator 装饰器用于定义一个函数,该函数将在字段赋值之前被调用。您可以使用它来执行自定义验证逻辑,例如检查值是否在特定范围内、是否符合特定格式等。

```python
from pydantic import BaseModel, validator

class User(BaseModel):
name: str
age: int

@validator("age")
def age_must_be_positive(cls, value):
    if value <= 0:
        raise ValueError("Age must be positive")
    return value

```

在这个例子中,age_must_be_positive 函数是一个字段级验证器,它检查 age 字段的值是否为正数。如果不是,它将引发 ValueError 异常。

@validator 装饰器还支持一些可选参数:

  • pre: 如果设置为 True,验证器将在 Pydantic 的内置类型转换之前运行。默认为False.
  • each_item: 如果字段是集合类型(如 list),是否对每个元素应用验证器, 默认False.
  • always: 是否始终运行验证器,即使字段没有被赋值。默认False.
  • check_fields: 是否检查字段是否存在。默认True.

2.3.2 @root_validator:模型级验证

@root_validator 装饰器用于定义一个函数,该函数将在整个模型验证完成后被调用。您可以使用它来执行跨字段的验证逻辑,例如检查多个字段之间的关系是否满足特定条件。

```python
from pydantic import BaseModel, root_validator

class Event(BaseModel):
start_time: datetime
end_time: datetime

@root_validator
def end_time_must_be_after_start_time(cls, values):
    if "start_time" in values and "end_time" in values and values["start_time"] >= values["end_time"]:
        raise ValueError("End time must be after start time")
    return values

``@root_validator装饰器也有pre参数, 如果设置为True`, 将在所有字段验证之前运行.

在这个例子中,end_time_must_be_after_start_time 函数是一个模型级验证器,它检查 end_time 是否晚于 start_time

3. 数据验证与解析:Pydantic 的核心功能

定义好 Pydantic 模型后,您就可以使用它来验证和解析数据了。Pydantic 提供了多种方法来完成这一任务:

3.1 构造函数:直接创建模型实例

最简单的方法是使用模型的构造函数来创建实例:

```python
data = {
"id": 123,
"name": "John Doe",
"signup_ts": "2023-10-27T10:00:00",
"friends": [1, 2, 3],
}

user = User(**data)

print(user.id) # 输出:123
print(user.signup_ts) # 输出:2023-10-27 10:00:00
```

Pydantic 将自动根据模型定义进行数据验证和类型转换。如果数据无效,Pydantic 将引发 ValidationError 异常。

3.2 parse_obj 方法:从字典解析

parse_obj 方法用于从字典创建模型实例:

```python
data = {
"id": 123,
"name": "John Doe",
"signup_ts": "2023-10-27T10:00:00",
"friends": [1, 2, 3],
}

user = User.parse_obj(data)
```

parse_obj 方法与构造函数类似,但它更明确地表明您正在从字典解析数据。

3.3 parse_raw 方法:从 JSON 字符串或文件解析

parse_raw 方法用于从 JSON 字符串或文件创建模型实例:

```python
json_data = '{"id": 123, "name": "John Doe", "signup_ts": "2023-10-27T10:00:00", "friends": [1, 2, 3]}'

user = User.parse_raw(json_data)

也可以传入文件路径

user = User.parse_raw("path/to/file.json")

```

3.4 parse_file 方法:从文件路径解析

parse_file 方法用于直接从文件路径创建模型实例

```python

user = User.parse_file("path/to/file.json")

```

3.5 ValidationError 异常:处理验证错误

如果数据无效,Pydantic 将引发 ValidationError 异常。该异常包含有关验证失败的详细信息,包括错误消息和字段路径。

```python
from pydantic import BaseModel, ValidationError

class User(BaseModel):
id: int
name: str

try:
user = User(id="abc", name="John Doe")
except ValidationError as e:
print(e)
```

输出:

1 validation error for User
id
value is not a valid integer (type=type_error.integer)

您可以使用 e.errors() 方法获取一个包含所有验证错误的列表。每个错误都是一个字典,包含以下键:

  • loc: 错误的位置(字段路径)。
  • msg: 错误消息。
  • type: 错误类型。

您还可以使用 e.json() 方法将错误信息转换为 JSON 格式。

4. 模型导出:将数据转换为字典或 JSON

Pydantic 模型提供了多种方法将数据转换为字典或 JSON 格式,方便与其他系统进行交互:

4.1 dict 方法:转换为字典

dict 方法用于将模型实例转换为字典:

```python
user = User(id=123, name="John Doe")
user_dict = user.dict()

print(user_dict) # 输出:{'id': 123, 'name': 'John Doe'}
``dict方法接受许多参数来定制输出. 常用参数有:
*
include: 包含的字段.
*
exclude: 排除的字段.
*
by_alias: 是否使用别名.
*
exclude_unset: 是否排除未设置的字段.
*
exclude_defaults: 是否排除默认值的字段.
*
exclude_none`: 是否排除值为 None 的字段.

4.2 json 方法:转换为 JSON 字符串

json 方法用于将模型实例转换为 JSON 字符串:

```python
user = User(id=123, name="John Doe")
user_json = user.json()

print(user_json) # 输出:{"id": 123, "name": "John Doe"}
``json方法接受dict方法的所有参数, 以及indent,separatorsjson.dumps` 方法的参数.

5. 高级特性:解锁 Pydantic 的更多潜力

除了上述核心功能外,Pydantic 还提供了许多高级特性,进一步增强了其灵活性和适用性:

5.1 泛型模型:支持泛型类型

Pydantic 支持泛型模型,允许您定义可以处理不同类型数据的模型。

```python
from typing import Generic, TypeVar, List
from pydantic.generics import GenericModel

T = TypeVar('T')

class ItemList(GenericModel, Generic[T]):
items: List[T]
```

在这个示例中我们定义了一个泛型模型 ItemList,它可以用于处理任何类型 T 的列表。

5.2 Config 类:全局配置

Pydantic 允许您通过 Config 类来配置模型的行为。Config 类可以作为 Pydantic 模型的内部类,也可以通过model_config属性访问。

```python
from pydantic import BaseModel

class User(BaseModel):
id: int
name: str

class Config:
    # 或者使用 model_config = { ... }
    orm_mode = True  # 启用 ORM 模式
    allow_population_by_field_name = True #允许按字段名填充

```

Config 类的常用选项包括:

  • orm_mode: 启用 ORM 模式,允许从 ORM 对象创建模型实例。
  • allow_mutation: 是否允许修改模型实例的字段值.
  • error_msg_templates: 自定义错误消息模板。
  • extra: 如何处理模型定义中未定义的字段 (allow, ignore, forbid)。
  • json_encoders: 自定义 JSON 编码器。
  • validate_all: 是否验证所有字段, 即使依赖的字段有错误, 默认False.
  • validate_assignment : 是否在赋值时进行验证, 默认False.

5.3 数据转换: 使用 BeforeValidator, AfterValidator, WrapValidatorPlainValidator

从 Pydantic v2 开始, 引入了 @pydantic.field_validator 装饰器, 以及 BeforeValidator, AfterValidator, WrapValidatorPlainValidator , 允许用户对数据进行更精细的转换和验证.

  • BeforeValidator: 在 Pydantic 进行类型校验和 coercion 之前执行.
  • AfterValidator: 在 Pydantic 进行类型校验和 coercion 之后执行.
  • WrapValidator: 同时包含 before 和 after 逻辑.
  • PlainValidator: 接收原始输入值.

```python
from typing import Any
from pydantic import BaseModel, BeforeValidator, AfterValidator, ValidationError

def to_upper(v: str) -> str:
return v.upper()

def double_value(v: int) -> int:
return v * 2

class MyModel(BaseModel):
name: str = BeforeValidator(to_upper)
value: int = AfterValidator(double_value)

m = MyModel(name='hello', value=5)
print(m)

> name='HELLO' value=10

```

6. 总结:Pydantic 的优势与应用

Pydantic 是一个功能强大且易于使用的 Python 数据验证和解析库。它通过类型注解简化了数据模型的定义,并自动执行数据验证、类型转换和文档生成。Pydantic 的高性能、可扩展性和广泛的应用场景使其成为构建健壮、可靠的应用程序的理想选择。

通过本文的介绍,您应该已经对 Pydantic 的核心概念、使用方法和高级特性有了全面的了解。希望您能够将 Pydantic 应用到您的项目中,提高开发效率和代码质量。

THE END