一、引言

以下Pydantic使用的版本为2.0.0,不同版本的Pydantic可能会有一些差异,特别是在一些新的功能和废弃的功能上,建议查看官方文档。

1.1 什么是Pydantic

Pydantic是Python中最广泛使用的基于Python类型提示的数据验证和设置管理库,它遵循纯正的Python 3.9+标准,能帮助开发者轻松验证数据、管理配置并减少样板代码。该库快速且可扩展,能与代码分析器、集成开发环境良好配合,便于进行数据验证工作。

1.2 为什么选择Pydantic

  • 由类型提示驱动:用Pydantic的时候,模式验证和序列化都靠类型注解来控制。就不用学太多东西,也不用写很多代码,还能和IDE以及静态分析工具完美配合。
  • 速度快:Pydantic的核心验证逻辑是用Rust写的,所以它是Python里最快的数据验证库之一。
  • 支持JSON Schema:Pydantic模型能生成JSON Schema,这样就能轻松和其他工具集成。
  • 严格模式和宽松模式:Pydantic有两种模式可以选。严格模式下,数据不会被转换;宽松模式下,Pydantic会在合适的时候把数据转换成正确的类型。
  • 支持多种标准库类型:Pydantic能验证很多标准库的类型,像数据类、类型字典这些都不在话下。
  • 可自定义:Pydantic允许自定义验证器和序列化器,用各种方式来改变数据处理的方法。
  • 生态系统强大:PyPI上大概有8000个包都在用Pydantic,像FastAPI、huggingface、Django Ninja、SQLModel和LangChain这些流行的库也都在用。
  • 经过实战检验:Pydantic每个月的下载量超过3.6亿次,FAANG所有公司,还有纳斯达克25家最大企业里的20家都在用它。

以上内容是摘自官网的,不过也确实很好用,在项目中用的比较多😄,特别是用Fast API做后端的时候,会帮你规范接收的JSON数据,多了一个校验层,减少了很多的错误。

1.3 应用场景

  • API请求和响应验证:这个也是我实际工作中用的比较多的,用Fast API作为后端接收数据时,会使用Pydantic来验证客户端发送的请求数据和服务器返回的响应数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from fastapi import FastAPI
from pydantic import BaseModel

# 创建FastAPI应用实例
app = FastAPI()

# 定义Pydantic模型
class Item(BaseModel):
name: str
price: float
is_offer: bool = None

# 创建路由,使用Pydantic模型进行请求体验证
@app.post("/items/")
async def create_item(item: Item):
# 这里可以处理item,例如保存到数据库
return {"item_name": item.name, "item_price": item.price, "is_offer": item.is_offer}

这个示例中的流程如下:

  1. 导入FastAPI和BaseModel
  2. 创建FastAPI应用实例
  3. 定义与之前相同的Item模型
  4. 创建POST路由,使用Item模型验证请求体
  5. 在路由处理函数中,直接使用验证后的item数据

当客户端发送POST请求到/items/端点时,FastAPI获取的数据会先通过Pydantic模型进行验证,如果不符合会返回详细的错误信息,这里的错误信息是Pydantic提供的,不是FastAPI提供的,Fast API作为后端获取客户端发来的数据。这也侧面说明Pydantic可以在很多场景下使用,不仅限于Fast API。

  • 配置管理:管理应用程序的配置,确保配置数据的正确性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
from pydantic import BaseModel, Field
from typing import Optional
from pydantic_settings import SettingsConfigDict

class Settings(BaseModel):
# 应用基本信息
app_name: str = "Awesome API"
app_version: str = "1.0.0"
debug: bool = False

# 管理员信息
admin_email: str
admin_phone: Optional[str] = None

# 数据库配置
db_host: str = "localhost"
db_port: int = 5432
db_username: str = "postgres"
db_password: str = Field(..., description="数据库密码,不应硬编码")
db_name: str = "myapp"

# API配置
items_per_user: int = 50
api_key: str = Field(..., description="API密钥,不应硬编码")
jwt_secret: str = Field(..., description="JWT密钥,不应硬编码")
jwt_expiry_minutes: int = 30

# Pydantic v2 配置方式
model_config = SettingsConfigDict(
env_file=".env", # 从.env文件加载环境变量
case_sensitive=True, # 区分大小写
env_prefix="APP_", # 环境变量前缀
extra="forbid" # 禁止未知字段
)

# 创建配置实例
# 注意:敏感信息如密码、密钥等不应硬编码
# 应该通过环境变量或.env文件提供
try:
settings = Settings()
print(f"应用名称: {settings.app_name}")
print(f"数据库连接: postgresql://{settings.db_username}:*****@{settings.db_host}:{settings.db_port}/{settings.db_name}")
except Exception as e:
print(f"配置加载失败: {e}")
"""
配置加载失败: 4 validation errors for Settings
admin_email
Field required [type=missing, input_value={}, input_type=dict]
For further information visit https://errors.pydantic.dev/2.11/v/missing
db_password
Field required [type=missing, input_value={}, input_type=dict]
For further information visit https://errors.pydantic.dev/2.11/v/missing
api_key
Field required [type=missing, input_value={}, input_type=dict]
For further information visit https://errors.pydantic.dev/2.11/v/missing
jwt_secret
Field required [type=missing, input_value={}, input_type=dict]
For further information visit https://errors.pydantic.dev/2.11/v/missing
"""

不难发现,Pydantic的配置管理功能非常强大,能够帮助我们更好的管理应用的配置,减少错误的发生。本质上还是做了数据的校验,只是校验的内容是配置项。配合其他库使用,会更加方便。

比如,你可以将这些配置与上面举例的FastAPI结合使用,例如:

1
2
3
4
5
6
7
from fastapi import FastAPI

app = FastAPI(title=settings.app_name, version=settings.app_version)

@app.get("/")
async def root():
return {"app_name": settings.app_name, "version": settings.app_version}

这样可以使你的应用配置更加规范和灵活,同时提高代码的可维护性和安全性。

在实际工程中可以单独开一个文件夹来存放用Pydantic规范好的数据模型,更好做数据类型的统一管理。

  • 数据序列化和反序列化:将数据在不同格式之间转换,如JSON和Python对象之间的转换。Pydantic 提供了丰富的序列化功能,具体如下:
    1. 转换为 Python 字典(包含相关 Python 对象):可将模型转换为由相关 Python 对象组成的 Python 字典。
    2. 转换为可 JSON 序列化的 Python 字典:能把模型转换为仅由“可转换为 JSON 的”类型组成的 Python 字典。
    3. 转换为 JSON 字符串:支持将模型直接转换为 JSON 字符串。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
from pydantic import BaseModel
from datetime import datetime
from typing import List

class User(BaseModel):
id: int
name: str
signup_date: datetime
tags: List[str]

# 1. 反序列化:JSON转Python对象
# 从JSON字符串创建模型实例
user_data = '{"id": 1, "name": "John Doe", "signup_date": "2023-01-01T12:00:00", "tags": ["premium", "active"]}'
user = User.model_validate_json(user_data)
print("反序列化结果:")
print(user)
print(f"用户ID: {user.id}, 名称: {user.name}")
print(f"注册日期类型: {type(user.signup_date)}")

# 2. 序列化功能展示
user_dict = user.model_dump()
print("\n转换为包含Python对象的字典:")
print(user_dict)
print(f"字典中的日期类型: {type(user_dict['signup_date'])}")

json_compatible_dict = user.model_dump(exclude_unset=True, by_alias=True)
print("\n转换为可JSON序列化的字典:")
print(json_compatible_dict)

user_json = user.model_dump_json()
print("\n转换为JSON字符串:")
print(user_json)

# 3. 高级用法:自定义JSON编码器
class CustomUser(BaseModel):
id: int
name: str
signup_date: datetime

class Config:
json_encoders = {
datetime: lambda v: v.strftime('%Y-%m-%d'),
}

custom_user = CustomUser(id=2, name="Jane Smith", signup_date=datetime.now())
custom_json = custom_user.model_dump_json(indent=2)
print("\n自定义JSON编码器结果:")
print(custom_json)

这些功能使得Pydantic成为处理数据验证和序列化的强大工具,特别适合在API开发、配置管理和数据交换场景中使用。

序列化和反序列化的功能在实际工程中用的比较多,比如在Web应用中,客户端发送的请求数据是JSON格式的,服务器端需要将JSON格式的数据转换为Python对象进行处理,处理完成后再转换为JSON格式返回给客户端。

  • 数值校验:对数值类型的字段进行校验,确保输入的数值在指定的范围内。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from typing import Any

from pydantic import BaseModel, field_validator


class Model(BaseModel):
value: str

@field_validator('value', mode='before')
@classmethod
def cast_ints(cls, value: Any) -> Any:
if isinstance(value, int):
return "这是数字"
else:
return "这是字符串"


print(Model(value='a'))
#> value='这是字符串'
print(Model(value=1))
#> value='这是数字'

这个示例更贴近实际应用场景,特别是在用户注册、表单提交等需要严格数据验证的场景中非常有用。通过Pydantic,我们可以轻松实现复杂的验证逻辑,同时保持代码的清晰和可维护性。

特意给了个奇怪的示例,其实是想说,既然我可以用这个来做判断,就可以做转换🤭,如果我把”这是字符串”和”这是数字”转换成对应的异常信息返回、或者是类型转换代码,是不是可以做到统一输入了,即使收到的数据类型不同也不会影响后续的代码运行结果。

  • 数据清洗和转换:清理和转换输入数据,使其符合预期格式。
1
2
3
4
5
6
7
8
from pydantic import BaseModel

class Data(BaseModel):
value: int

# 数据清洗和转换,将字符串转换为整数
data = Data(value="42")
print(data.value)

当然Pydantic本身也具备自动转换数据类型的能力,上面这个例子就是把字符串”42”转换为整数42。Pydantic很强大,有很多潜在的能力等待大家发掘,具体可以看看Pydantic官网,这里就不把全部特性都放出来了,篇幅很大。

二、安装与基础配置

2.1 安装Pydantic

1
pip install pydantic

Pydantic也支持conda安装,命令如下:

1
conda install pydantic -c conda-forge

Pydantic具有以下可选依赖项:

  • email:由emailValidator程序包提供的电子邮件验证。
  • timezone:由tzdata程序包提供的IANA时区数据库的备用选项。
    要与Pydantic一起安装可选的依赖项:
1
2
3
4
# 可以只安装email验证器
pip install 'pydantic[email]'
# 也可以都安装,自己选择即可
pip install 'pydantic[email,timezone]'

三、核心功能与优势

最后就把Pydantic的核心功能和优势总结一下,上面的应用场景其实都有涉及,大家看完下面的功能会有更清晰的认识。

3.1 数据验证

Pydantic 提供了强大而灵活的数据验证机制,确保输入数据符合预期格式和约束。

  • 内置验证器:提供了丰富的预定义验证器,如 EmailStr(邮箱验证)、UrlStr(URL验证)、constr(字符串约束)、conint(整数约束)等,覆盖了大多数常见验证场景。
  • 自定义验证器:通过 @validator 装饰器可以轻松创建自定义验证逻辑,支持字段级和模型级验证,还可以访问其他字段的值进行联合验证。
  • 验证错误处理:验证失败时会生成结构化的ValidationError对象,包含详细的错误信息(字段、错误类型、错误消息),便于定位和处理问题。

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from pydantic import BaseModel, EmailStr, field_validator, ValidationError
from pydantic_core.core_schema import FieldValidationInfo

class User(BaseModel):
email: EmailStr
age: int
password: str

@field_validator('age')
def age_must_be_adult(cls, v):
if v < 18:
raise ValueError('必须是成年人')
return v

@field_validator('password')
def password_must_be_strong(cls, v):
if len(v) < 8:
raise ValueError('密码长度必须至少为8位')
if not any(c.isupper() for c in v):
raise ValueError('密码必须包含至少一个大写字母')
return v

# 验证失败示例
try:
User(email='invalid-email', age=16, password='weak')
except ValidationError as e:
print(e.json())

3.2 类型提示与自动转换

Pydantic 充分利用 Python 的类型提示系统,提供静态类型检查和运行时类型转换的双重保障。

  • 支持的类型:全面支持 Python 内置类型(int, float, str, bool 等)、标准库类型(datetime, date, UUID 等)以及自定义类型。
  • 自动类型转换:智能地将输入数据转换为声明的类型,例如将字符串形式的数字转换为整数,将ISO格式的字符串转换为 datetime 对象。
  • 复杂类型处理:优雅处理嵌套列表、字典、集合等复杂数据结构,支持递归类型定义和类型参数化。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pydantic import BaseModel
from datetime import datetime
from typing import List, Dict, Optional

class Event(BaseModel):
id: int
name: str
start_time: datetime # 自动将字符串转换为datetime
participants: List[str] # 支持列表类型
metadata: Optional[Dict[str, str]] = None # 支持可选字段和字典类型

# 类型转换示例
event = Event(
id='123', # 字符串自动转换为整数
name='技术研讨会',
start_time='2023-12-01T10:00:00', # 字符串自动转换为datetime
participants=['张三', '李四'],
metadata={'location': '线上', 'organizer': '技术部'}
)
print(event.model_dump())

3.3 模型定义与管理

Pydantic 模型是数据结构和业务逻辑的核心载体,支持多种高级特性。

  • 基础模型:通过继承 BaseModel 创建,字段使用类型注解定义,支持默认值、可选字段和字段校验器。
  • 嵌套模型:模型字段可以是另一个模型类型,实现数据结构的层级化和模块化,便于处理复杂数据。
  • 继承模型:支持模型间的继承,子类模型自动继承父类的字段和验证器,也可以覆盖或扩展父类行为。
  • 泛型模型:支持泛型类型,提高代码复用性,适用于处理不同类型但结构相似的数据。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
from pydantic import BaseModel
from typing import List, TypeVar, Generic

# 基础模型
class Person(BaseModel):
name: str
age: int

# 嵌套模型
class Address(BaseModel):
city: str
street: str
zip_code: str

class Contact(BaseModel):
person: Person # 嵌套Person模型
address: Address # 嵌套Address模型
phone: str

# 继承模型
class Employee(Person):
employee_id: str
department: str

# 泛型模型
data_type = TypeVar('data_type')

class PaginatedResponse(BaseModel, Generic[data_type]):
total: int
page: int
page_size: int
items: List[data_type]

# 使用泛型模型
users = [Person(name='张三', age=30), Person(name='李四', age=25)]
response = PaginatedResponse[Person](total=2, page=1, page_size=10, items=users)
# 输出模型内容
print(response.model_dump())

3.4 与其他库的兼容性

Pydantic 设计为与其他流行 Python 库无缝集成,增强开发体验和效率。

  • 与FastAPI集成:FastAPI 原生支持 Pydantic 模型,自动用于请求参数验证和响应格式化,大幅减少重复代码。
  • 与Django/Flask集成:可作为表单验证和API数据处理的补充,在保持框架原生体验的同时提升数据验证能力。
  • 与Pandas/Numpy集成:支持与数据科学库的数据结构互转,方便在数据处理管道中进行数据验证和清洗。

示例(与FastAPI集成):

1
2
3
4
5
6
7
8
9
10
11
12
13
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
name: str
price: float
is_offer: bool = None

@app.post('/items/')
async def create_item(item: Item):
return {'item_name': item.name, 'item_price': item.price}