Pydantic 是一个 Python 库,用于数据验证设置管理,它利用 Python 的类型提示 (type hints) 来定义数据模式。Pydantic 在运行时强制执行类型提示,并为您的数据提供友好的错误信息,使得数据模型更加健壮、可维护和自文档化。它广泛应用于 Web API (如 FastAPI)、数据科学、配置管理等领域。

核心思想:将 Python 的类型提示转化为强大的运行时数据验证和序列化工具,从而提高代码的健壮性和开发效率。


一、为什么需要 Pydantic?

在现代 Python 应用开发中,数据从外部来源(如 JSON API、数据库、配置文件、用户输入)进入系统是常态。这些外部数据往往不可信,结构复杂且容易出错。传统的 Python 处理方式存在一些问题:

  1. 缺乏数据验证:直接使用字典或弱类型对象,无法保证数据的结构和类型正确性,容易导致运行时错误。
  2. 手动验证繁琐:编写大量的 if/else 语句进行数据类型检查和值验证,导致代码冗长、难以维护。
  3. 序列化/反序列化复杂:将 Python 对象转换为 JSON/XML 或反之,需要手动处理字段映射和类型转换。
  4. 配置管理混乱:应用配置往往散落在字典或环境变量中,缺乏统一的验证和管理机制。

Pydantic 旨在解决这些问题,提供以下优势:

  • 运行时类型检查:强制执行 Python 类型提示,在数据加载时即捕获类型错误。
  • 清晰的数据模型:使用 Python 类和类型提示直观定义数据结构,自文档化。
  • 自动数据转换:尝试将输入数据转换为模型中定义的正确类型(例如,将 “123” 转换为 int)。
  • 友好的错误报告:当数据不符合模型时,生成详细、易读的验证错误信息。
  • 兼容性好:兼容 Python 标准库 typing 模块,与其他类型提示工具(如 MyPy)配合良好。
  • 灵活的配置管理:轻松从环境变量、文件等加载配置。
  • 与 FastAPI 无缝集成:Pydantic 是 FastAPI 的核心组件,用于请求体、响应体、路径参数等的自动验证和序列化。

二、安装 Pydantic

Pydantic 可以通过 pip 安装:

1
pip install pydantic

三、基本使用:定义数据模型 (BaseModel)

Pydantic 的核心是 BaseModel。通过继承 BaseModel 并使用标准 Python 类型提示定义类属性,即可创建一个数据模型。

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
from pydantic import BaseModel, Field, EmailStr, ValidationError
from typing import List, Optional, Dict, Union

# 1. 定义一个基本的用户模型
class User(BaseModel):
id: int
name: str = "Anonymous" # 默认值,如果未提供则使用
email: EmailStr # Pydantic 内置的 Email 类型验证器
age: Optional[int] = None # Optional 表示可以是 int 或 None
is_active: bool = True

# 2. 从字典创建模型实例 (数据验证在此处发生)
try:
user_data = {
"id": 123,
"name": "Alice",
"email": "alice@example.com",
"age": 30
}
user = User(**user_data) # **user_data 会将字典解包为关键字参数

print(f"用户 ID: {user.id}, 类型: {type(user.id)}")
print(f"用户姓名: {user.name}, 类型: {type(user.name)}")
print(f"用户邮箱: {user.email}, 类型: {type(user.email)}")
print(f"用户年龄: {user.age}, 类型: {type(user.age)}")
print(f"用户是否活跃: {user.is_active}, 类型: {type(user.is_active)}")
print(f"模型实例: {user.model_dump_json(indent=2)}") # Pydantic v2: model_dump_json
# Pydantic v1: user.json(indent=2)

# 尝试创建不含 name 的实例,会使用默认值
user_no_name = User(id=456, email="bob@example.com", age=25)
print(f"不含 name 的用户: {user_no_name.name}") # Bob

except ValidationError as e:
print(f"数据验证错误: {e}")

# 3. 数据验证失败示例
try:
invalid_user_data = {
"id": "abc", # id 应该是 int
"email": "invalid-email", # 无效的邮箱格式
"age": "twenty" # age 应该是 int
}
invalid_user = User(**invalid_user_data)
except ValidationError as e:
print("\n--- 验证失败示例 ---")
print(e.json(indent=2)) # 输出详细的 JSON 格式错误信息
# [
# {
# "type": "int_parsing",
# "loc": [
# "id"
# ],
# "msg": "Input should be a valid integer, unable to parse string 'abc'",
# "input": "abc"
# },
# {
# "type": "string_pattern_mismatch",
# "loc": [
# "email"
# ],
# "msg": "String should match pattern '\\^.+@.+\\.\\S+\\$'", # 邮箱正则匹配失败
# "input": "invalid-email"
# },
# {
# "type": "int_parsing",
# "loc": [
# "age"
# ],
# "msg": "Input should be a valid integer, unable to parse string 'twenty'",
# "input": "twenty"
# }
# ]

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

class UserProfile(BaseModel):
user_details: User
home_address: Address
tags: List[str] = [] # 列表类型
params: Dict[str, Union[int, str]] # 字典类型,值可以是 int 或 str

try:
profile_data = {
"user_details": {
"id": 1,
"email": "jane@example.com"
},
"home_address": {
"street": "123 Main St",
"city": "Anytown",
"zip_code": "12345"
},
"tags": ["developer", "python"],
"params": {"level": 10, "status": "active"}
}
profile = UserProfile(**profile_data)
print("\n--- 嵌套模型示例 ---")
print(profile.model_dump_json(indent=2))
except ValidationError as e:
print(f"嵌套模型验证错误: {e}")

关键点:

  • 类型提示id: int, name: str 定义了字段的预期类型。
  • 默认值name: str = "Anonymous" 为字段提供了默认值。
  • Optionalage: Optional[int] = None 表示 age 字段可以为 int 类型,也可以为 None
  • 内置类型和验证器:Pydantic 提供了像 EmailStr 这样的特殊类型,自动进行格式验证。
  • 数据转换:Pydantic 会尝试将传入的数据转换为目标类型。例如,如果 id: int 但传入 "123",Pydantic 会自动将其转换为整数。
  • 错误信息:验证失败时,会抛出 ValidationError 并提供详细的错误列表。
  • 嵌套模型:模型可以相互嵌套,轻松构建复杂的数据结构。

四、字段验证 (Field)

除了基本的类型验证,Pydantic 还允许通过 Field 函数为字段添加更细粒度的验证规则、别名和元数据。

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
from pydantic import BaseModel, Field, ValidationError

class Item(BaseModel):
name: str = Field(min_length=3, max_length=50, description="商品的名称")
price: float = Field(gt=0, description="商品的价格,必须大于0") # gt: greater than
rating: int = Field(ge=1, le=5, description="商品的评分,1到5之间") # ge: greater equal, le: less equal
item_id: str = Field(alias="id_") # 使用 id_ 作为别名,实际数据传入 id_

try:
item_data_valid = {
"id_": "item001", # 注意这里传入的是别名
"name": "Laptop",
"price": 999.99,
"rating": 4
}
item = Item(**item_data_valid)
print(f"有效商品: {item.model_dump_json(indent=2)}")
print(f"访问 item.name: {item.name}") # 依然通过 name 访问

item_data_invalid_price = {
"id_": "item002",
"name": "Book",
"price": -10.0, # 无效价格
"rating": 3
}
invalid_item = Item(**item_data_invalid_price)
except ValidationError as e:
print("\n--- Field 验证失败示例 ---")
print(e.json(indent=2))

try:
item_data_invalid_name = {
"id_": "item003",
"name": "a", # 长度小于3
"price": 100,
"rating": 3
}
invalid_item = Item(**item_data_invalid_name)
except ValidationError as e:
print("\n--- Field 字符串长度验证失败示例 ---")
print(e.json(indent=2))

Field 函数的常见参数:

  • default: 默认值。
  • default_factory: 默认为可调用对象,每次访问时生成新默认值。
  • alias: 字段的别名,在数据加载时使用,但访问时仍用原字段名。
  • title, description: 用于文档生成(如 FastAPI 的 OpenAPI 文档)。
  • min_length, max_length: 字符串长度限制。
  • gt, ge, lt, le: 数值大小限制 (大于, 大于等于, 小于, 小于等于)。
  • multiple_of: 数值必须是某个数的倍数。
  • pattern: 字符串必须匹配的正则表达式。

五、Pydantic Settings (配置管理)

Pydantic 的 BaseSettings(Pydantic v2 中已解耦为 pydantic-settings)是其一大亮点,用于优雅地管理应用程序配置。它能够自动从环境变量、.env 文件等加载配置,并进行验证。

需要单独安装 pydantic-settings

1
pip install pydantic-settings
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
from pydantic_settings import BaseSettings, SettingsConfigDict
from typing import Optional

# 为了方便演示,这里不实际创建 .env 文件,而是直接修改环境变量。
# 在实际项目中,通常会创建一个 .env 文件,内容如下:
# APP_NAME=My Awesome App
# DEBUG_MODE=True
# DATABASE_URL=postgresql://user:pass@host:port/db
# PORT=8000

# 从 .env 文件加载 (如果存在)
# os.environ["APP_NAME"] = "TestApp" # 模拟设置环境变量
# os.environ["DEBUG_MODE"] = "True"
# os.environ["PORT"] = "8000"
# os.environ["SECRET_KEY"] = "super-secret"


class AppSettings(BaseSettings):
app_name: str = "Default App"
debug_mode: bool = False
database_url: Optional[str] = None
port: int = 8000
secret_key: str # 必需的配置

# Pydantic v2 配置模型来源
model_config = SettingsConfigDict(
env_file='.env', # 指定从 .env 文件加载
env_file_encoding='utf-8',
extra='ignore', # 忽略 .env 中模型未定义的其他变量
case_sensitive=False # 环境变量名称不区分大小写
)
# Pydantic v1 配置模型来源
# class Config:
# env_file = '.env'
# env_file_encoding = 'utf-8'


try:
# 模拟从环境变量加载 (Pydantic 会自动查找)
# 假设环境变量已设置:
# export APP_NAME="My_Production_App"
# export SECRET_KEY="very_secure_key"
settings = AppSettings()
print("\n--- 应用设置示例 ---")
print(f"App Name: {settings.app_name}")
print(f"Debug Mode: {settings.debug_mode}")
print(f"Database URL: {settings.database_url}")
print(f"Port: {settings.port}")
print(f"Secret Key: {settings.secret_key}")

# Pydantic v2
print(f"设置模型 JSON: {settings.model_dump_json(indent=2)}")

# 验证失败示例: 如果 SECRET_KEY 未设置
except ValidationError as e:
print("\n--- 设置加载失败示例 ---")
print(f"缺少必要的配置项: {e.json(indent=2)}")

关键点:

  • BaseSettings:继承自 BaseSettings
  • SettingsConfigDict (v2) / Config (v1):内部类用于配置设置的加载行为,如指定 .env 文件路径。
  • 加载优先级:Pydantic 会按一定优先级加载配置:
    1. 直接作为参数传入 AppSettings()
    2. 环境变量。
    3. .env 文件。
    4. 字段的默认值。
  • 自动类型转换:环境变量的值(通常是字符串)会根据模型中的类型提示自动转换(例如 “TRUE” 自动转为 True,”8000” 自动转为 8000)。这省去了大量的 os.getenv() 和手动转换操作。

六、与 FastAPI 的集成

Pydantic 是 FastAPI 的核心,它几乎在所有数据处理环节都发挥作用:

  1. 请求体 (Request Body) 验证

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

    app = FastAPI()

    class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: float
    tax: Optional[float] = None

    @app.post("/items/")
    async def create_item(item: Item): # FastAPI 会自动使用 Pydantic 验证请求体 JSON
    return item.model_dump() # Pydantic v2: item.model_dump()
    # Pydantic v1: item.dict()

    当接收到一个 POST 请求时,FastAPI 会尝试将请求体 JSON 数据验证为 Item 模型的实例。如果失败,会自动返回包含详细错误信息的 422 Unprocessable Entity 响应。

  2. 响应模型 (Response Model)

    1
    2
    3
    4
    5
    @app.get("/items/{item_id}", response_model=Item) # 声明响应体应符合 Item 模型
    async def read_item(item_id: int):
    # 实际数据可能来自数据库或计算
    fake_db_item = {"name": "Foo", "price": 50.2, "item_id": item_id}
    return fake_db_item # FastAPI 会自动验证并序列化为 Item 模型

    response_model 参数用于确保从 read_item 返回的数据符合 Item 模型的结构,并将其序列化。

  3. 路径参数和查询参数验证:Pydantic 类型提示也会用于路径和查询参数。

    1
    2
    3
    4
    5
    @app.get("/users/{user_id}/items/")
    async def read_user_items(user_id: int, q: Optional[str] = None):
    # user_id 会被验证为 int
    # q 会被验证为 str 或 None
    return {"user_id": user_id, "q": q}

七、Pydantic v2 的新特性 (与 v1 对比)

Pydantic v2 在性能和功能上带来了显著改进,主要基于 Rust 重写了核心验证逻辑,并引入了一些 API 变更:

  • 性能提升:核心验证引擎用 Rust 编写,性能比 v1 提高了 5-50 倍。
  • 更一致的 API
    • dict() -> model_dump()
    • json() -> model_dump_json()
    • parse_obj() -> model_validate()
    • parse_raw() -> model_validate_json()
    • Config -> model_config (类属性) 和 SettingsConfigDict (用于 BaseSettings)。
  • Field 变更Field(**extra_attrs) 不再合法,应使用 JsonSchemaExtraextra 字典。
  • 更强大的数据类型:引入更多内置类型。
  • 更灵活的验证器:支持更复杂的自定义验证逻辑。

迁移建议:对于新项目,强烈推荐直接使用 Pydantic v2。对于现有 v1 项目,可以先进行小的增量迁移,或使用 pydantic-settings 替代 BaseSettings

八、总结与进阶

Pydantic 不仅仅是一个验证库,它更是一种编写健壮、可维护和自文档化 Python 代码的范式。通过统一数据模型、强制类型检查和自动化序列化/反序列化,它极大地提高了开发效率和代码质量。

进阶方向:

  • 自定义验证器 (@model_validator, @field_validator):编写自己的验证逻辑。
  • ComputedField (v2):根据其他字段计算得出新字段。
  • model_post_init (v2):在模型初始化后执行额外逻辑。
  • ConfigDict (v2):探索更多模型配置选项,如 extra='allow'/'forbid'/'ignore', frozen=True
  • 泛型模型 (GenericModel):创建可重用的泛型数据结构。
  • 与 ORM/ODM 集成:结合 SQLAlchemy 等数据库工具。
  • 协议缓冲/序列化:用于更高效的数据传输。

无论你是在构建 Web API、处理数据管道还是管理复杂的应用程序配置,Pydantic 都是你工具箱中不可或缺的强大利器。