1. 什么是 Pydantic? #
1.1 简单理解 #
Pydantic 是一个 Python 库,它的主要作用是验证数据是否正确。想象一下,你有一个表单,用户需要填写姓名、年龄、邮箱等信息。Pydantic 就像一个严格的检查员,确保用户输入的数据符合要求:
- 年龄必须是数字,不能是文字
- 邮箱必须包含 @ 符号
- 姓名不能为空
如果数据不符合要求,Pydantic 会立即告诉你哪里出错了。
1.2 核心特点 #
- 基于类型注解:使用 Python 的类型提示来定义数据应该是什么样子
- 自动验证:创建对象时自动检查数据是否正确
- 类型转换:如果可能,会自动将数据转换为正确的类型(比如把字符串 "123" 转换为数字 123)
- 易于使用:代码简洁,容易理解
1.3 类比理解 #
把 Pydantic 想象成一个智能的表格模板:
- 你定义好表格的格式(哪些列是必需的,哪些列是什么类型)
- 当有人填写表格时,Pydantic 自动检查:
- 必填项是否都填了?
- 数字列填的是数字吗?
- 邮箱格式正确吗?
- 如果填写错误,立即提示问题在哪里
2. 为什么需要 Pydantic? #
2.1 传统方式的问题 #
在 Python 中,如果不使用 Pydantic,验证数据通常需要写很多 if-else 判断:
# 传统方式:手动验证数据
def create_user(user_data):
# 检查 id 是否存在
if 'id' not in user_data:
raise ValueError("缺少 id")
# 检查 id 是否是整数
if not isinstance(user_data['id'], int):
raise ValueError("id 必须是整数")
# 检查 name 是否存在
if 'name' not in user_data:
raise ValueError("缺少 name")
# 检查 name 是否是字符串
if not isinstance(user_data['name'], str):
raise ValueError("name 必须是字符串")
# ... 还有很多检查 ...
return user_data这种方式的问题:
- 代码冗长:需要写很多重复的检查代码
- 容易出错:可能忘记检查某些字段
- 难以维护:当数据结构改变时,需要修改很多地方
2.2 Pydantic 的优势 #
使用 Pydantic,同样的功能只需要几行代码:
from pydantic import BaseModel
# 定义数据模型
class User(BaseModel):
id: int
name: str
# 自动验证
user = User(id=1, name="张三") # 自动检查类型优势:
- 代码简洁:定义一次,自动验证
- 自动类型检查:不需要手动写 if-else
- 清晰的错误信息:如果数据错误,会告诉你具体哪里错了
- 易于维护:修改模型定义即可
3. 前置知识 #
在学习 Pydantic 之前,你需要了解一些 Python 基础知识。如果你已经熟悉这些内容,可以跳过。
3.1 Python 类型注解(Type Hints) #
类型注解是 Python 3.5+ 引入的功能,用来标注变量、函数参数和返回值的类型。
3.1.1 基本类型注解 #
# 标注变量的类型
name: str = "张三" # name 是字符串类型
age: int = 25 # age 是整数类型
price: float = 99.99 # price 是浮点数类型
is_active: bool = True # is_active 是布尔类型
# 标注函数参数和返回值的类型
def add(a: int, b: int) -> int:
"""
函数 add 接收两个整数参数 a 和 b,返回一个整数
"""
return a + b
result = add(3, 5) # result 是 83.1.2 复杂类型注解 #
from typing import List, Dict, Optional
# List[str] 表示字符串列表
names: List[str] = ["张三", "李四", "王五"]
# Dict[str, int] 表示字典,键是字符串,值是整数
scores: Dict[str, int] = {"张三": 90, "李四": 85}
# Optional[str] 表示可以是字符串,也可以是 None
email: Optional[str] = None # 可以是字符串,也可以是 None
email = "zhangsan@example.com" # 也可以赋值字符串注意:类型注解不会影响程序运行,只是给开发者和工具(如 Pydantic)提供信息。
3.2 Python 类的基础知识 #
3.2.1 定义类 #
# 定义一个简单的类
class Person:
# __init__ 是构造函数,创建对象时自动调用
def __init__(self, name: str, age: int):
# self 表示对象本身
self.name = name # 设置对象的 name 属性
self.age = age # 设置对象的 age 属性
# 定义方法
def introduce(self):
return f"我是{self.name},今年{self.age}岁"
# 创建对象
person = Person("张三", 25)
print(person.introduce()) # 输出: 我是张三,今年25岁3.2.2 类的继承 #
# 父类(基类)
class Animal:
def __init__(self, name: str):
self.name = name
def speak(self):
return "动物在叫"
# 子类(派生类),继承自 Animal
class Dog(Animal):
def speak(self):
return f"{self.name}在汪汪叫"
# 创建子类对象
dog = Dog("旺财")
print(dog.speak()) # 输出: 旺财在汪汪叫Pydantic 中的 BaseModel 就是一个基类,我们定义的模型类继承自它,就自动获得了数据验证等功能。
3.3 字典解包(**kwargs) #
在 Python 中,** 用于解包字典。
# 定义一个字典
user_data = {"id": 1, "name": "张三", "age": 25}
# 传统方式:逐个传递参数
def create_user(id, name, age):
return {"id": id, "name": name, "age": age}
user1 = create_user(user_data["id"], user_data["name"], user_data["age"])
# 使用 ** 解包字典,自动传递所有键值对
user2 = create_user(**user_data) # 等同于 create_user(id=1, name="张三", age=25)在 Pydantic 中,我们经常用 ** 从字典创建对象:
user = User(**user_data) # 从字典创建 User 对象3.4 装饰器(Decorator) #
装饰器是 Python 的高级特性,用于修改函数或类的行为。
# 定义一个装饰器
def my_decorator(func):
def wrapper(*args, **kwargs):
print("函数执行前")
result = func(*args, **kwargs)
print("函数执行后")
return result
return wrapper
# 使用装饰器
@my_decorator
def say_hello():
print("Hello!")
say_hello()
# 输出:
# 函数执行前
# Hello!
# 函数执行后在 Pydantic 中,@validator 是一个装饰器,用于定义字段验证函数。
4. 安装 Pydantic #
4.1 使用 pip 安装 #
4.1.1 Windows 系统 #
打开命令提示符(CMD)或 PowerShell:
# 安装 Pydantic
pip install pydantic如果使用 Python 3,可能需要使用 pip3:
pip3 install pydantic4.1.2 Mac 系统 #
打开终端(Terminal):
# 安装 Pydantic
pip3 install pydantic4.2 验证安装 #
创建一个测试文件 test_pydantic.py:
# 导入 pydantic,检查是否安装成功
try:
from pydantic import BaseModel
print("Pydantic 安装成功!")
except ImportError:
print("Pydantic 未安装,请运行: pip install pydantic")运行测试:
Windows:
python test_pydantic.pyMac:
python3 test_pydantic.py如果看到 "Pydantic 安装成功!",说明安装正确。
5. 快速开始 #
让我们通过一个简单的例子快速了解 Pydantic 的基本用法。
5.1 用户信息验证 #
创建一个文件 example_01_basic.py:
# ============================================
# 第一个 Pydantic 例子:用户信息验证
# ============================================
# 导入 BaseModel,这是 Pydantic 的基础类
from pydantic import BaseModel
# 定义一个用户模型
# 继承 BaseModel 后,这个类就自动具有数据验证功能
class User(BaseModel):
# id 字段必须是整数类型
id: int
# name 字段必须是字符串类型
name: str
# email 字段必须是字符串类型
email: str
# age 字段是整数类型,默认值是 18
# 如果创建对象时不提供 age,会自动使用默认值
age: int = 18
# 示例1:创建用户对象(所有数据都正确)
print("=" * 50)
print("示例1:创建用户对象(数据正确)")
print("=" * 50)
user1 = User(id=1, name="张三", email="zhangsan@example.com", age=25)
print(f"用户对象: {user1}")
print(f"用户姓名: {user1.name}")
print(f"用户年龄: {user1.age}")
# 示例2:使用默认值(不提供 age)
print("\n" + "=" * 50)
print("示例2:使用默认值")
print("=" * 50)
user2 = User(id=2, name="李四", email="lisi@example.com")
print(f"用户对象: {user2}")
print(f"用户年龄(使用默认值): {user2.age}")
# 示例3:从字典创建对象
print("\n" + "=" * 50)
print("示例3:从字典创建对象")
print("=" * 50)
user_data = {
"id": 3,
"name": "王五",
"email": "wangwu@example.com",
"age": 30
}
# 使用 ** 解包字典,将字典的键值对作为参数传递
user3 = User(**user_data)
print(f"用户对象: {user3}")
# 示例4:数据验证(错误的数据)
print("\n" + "=" * 50)
print("示例4:数据验证(错误的数据)")
print("=" * 50)
try:
# 尝试用字符串作为 id(应该是整数)
user4 = User(id="不是数字", name="赵六", email="zhaoliu@example.com")
except Exception as e:
# 捕获异常并打印错误信息
print(f"验证错误: {e}")
print("\nPydantic 自动检测到 id 应该是整数,但提供了字符串!")
# 示例5:缺少必需字段
print("\n" + "=" * 50)
print("示例5:缺少必需字段")
print("=" * 50)
try:
# 缺少必需的 email 字段
user5 = User(id=5, name="孙七")
except Exception as e:
print(f"验证错误: {e}")
print("\nPydantic 检测到缺少必需的 email 字段!")5.2 运行代码 #
Windows:
python example_01_basic.pyMac:
python3 example_01_basic.py5.3 运行结果 #
==================================================
示例1:创建用户对象(数据正确)
==================================================
用户对象: id=1 name='张三' email='zhangsan@example.com' age=25
用户姓名: 张三
用户年龄: 25
==================================================
示例2:使用默认值
==================================================
用户对象: id=2 name='李四' email='lisi@example.com' age=18
用户年龄(使用默认值): 18
==================================================
示例3:从字典创建对象
==================================================
用户对象: id=3 name='王五' email='wangwu@example.com' age=30
==================================================
示例4:数据验证(错误的数据)
==================================================
验证错误: 1 validation error for User
id
value is not a valid integer (type=type_error.integer)
Pydantic 自动检测到 id 应该是整数,但提供了字符串!
==================================================
示例5:缺少必需字段
==================================================
验证错误: 1 validation error for User
email
field required (type=value_error.missing)
Pydantic 检测到缺少必需的 email 字段!5.4 关键点理解 #
- 定义模型:继承
BaseModel并定义字段类型 - 自动验证:创建对象时自动检查数据类型
- 默认值:可以为字段设置默认值
- 错误提示:数据错误时,Pydantic 会给出清晰的错误信息
6. 核心功能 #
6.1 数据验证 #
Pydantic 的核心功能是自动验证数据。让我们详细看看它是如何工作的。
6.1.1 类型验证 #
创建文件 example_02_validation.py:
# ============================================
# 数据验证
# ============================================
from pydantic import BaseModel, ValidationError
# 定义一个产品模型
class Product(BaseModel):
# 产品名称必须是字符串
name: str
# 价格必须是浮点数
price: float
# 库存数量必须是整数
stock: int
# 示例1:正确的数据
print("=" * 50)
print("示例1:正确的数据")
print("=" * 50)
product1 = Product(name="笔记本电脑", price=5999.99, stock=10)
print(f"产品: {product1}")
print(f"产品名称类型: {type(product1.name)}")
print(f"产品价格类型: {type(product1.price)}")
# 示例2:自动类型转换
print("\n" + "=" * 50)
print("示例2:自动类型转换")
print("=" * 50)
# 注意:price 传入的是字符串 "5999.99",但 Pydantic 会自动转换为浮点数
# stock 传入的是字符串 "10",但 Pydantic 会自动转换为整数
product2 = Product(name="鼠标", price="29.99", stock="5")
print(f"产品: {product2}")
print(f"价格类型(已自动转换): {type(product2.price)}")
print(f"库存类型(已自动转换): {type(product2.stock)}")
# 示例3:无法转换的类型错误
print("\n" + "=" * 50)
print("示例3:无法转换的类型错误")
print("=" * 50)
try:
# 尝试将 "不是数字" 转换为整数,会失败
product3 = Product(name="键盘", price=99.99, stock="不是数字")
except ValidationError as e:
# ValidationError 是 Pydantic 的验证错误类型
print("验证失败!")
print(f"错误详情: {e}")
# 可以获取更详细的错误信息
print("\n详细错误列表:")
for error in e.errors():
print(f" 字段: {error['loc']}")
print(f" 错误类型: {error['type']}")
print(f" 错误信息: {error['msg']}")
# 示例4:缺少必需字段
print("\n" + "=" * 50)
print("示例4:缺少必需字段")
print("=" * 50)
try:
# 缺少必需的 stock 字段
product4 = Product(name="显示器", price=1999.99)
except ValidationError as e:
print("验证失败!缺少必需字段")
print(f"错误: {e}")
# 示例5:使用 Optional 定义可选字段
print("\n" + "=" * 50)
print("示例5:可选字段")
print("=" * 50)
from typing import Optional
# 定义一个新模型,description 是可选的
class ProductWithDescription(BaseModel):
name: str
price: float
# Optional[str] 表示可以是字符串,也可以是 None
# = None 表示默认值是 None
description: Optional[str] = None
# 不提供 description(使用默认值 None)
product5 = ProductWithDescription(name="耳机", price=299.99)
print(f"产品(无描述): {product5}")
# 提供 description
product6 = ProductWithDescription(name="音响", price=599.99, description="高品质蓝牙音响")
print(f"产品(有描述): {product6}")6.1.2 运行结果 #
运行 python example_02_validation.py(Windows)或 python3 example_02_validation.py(Mac),你会看到详细的验证过程。
6.2 数据序列化 #
序列化是将对象转换为可以存储或传输的格式(如字典、JSON)。反序列化是相反的过程。
创建文件 example_03_serialization.py:
# ============================================
# 数据序列化和反序列化
# ============================================
from pydantic import BaseModel
import json
# 定义用户模型
class User(BaseModel):
id: int
name: str
email: str
age: int = 18
# 创建一个用户对象
user = User(id=1, name="张三", email="zhangsan@example.com", age=25)
print("=" * 50)
print("原始对象")
print("=" * 50)
print(f"用户对象: {user}")
print(f"对象类型: {type(user)}")
# 方法1:转换为字典
print("\n" + "=" * 50)
print("方法1:转换为字典(dict())")
print("=" * 50)
# dict() 方法将 Pydantic 对象转换为普通字典
user_dict = user.dict()
print(f"字典: {user_dict}")
print(f"字典类型: {type(user_dict)}")
print(f"可以像普通字典一样访问: {user_dict['name']}")
# 方法2:转换为 JSON 字符串
print("\n" + "=" * 50)
print("方法2:转换为 JSON 字符串(json())")
print("=" * 50)
# json() 方法将对象转换为 JSON 格式的字符串
user_json = user.json()
print(f"JSON 字符串: {user_json}")
print(f"JSON 类型: {type(user_json)}")
# JSON 字符串可以保存到文件或通过网络传输
# 方法3:转换为格式化的 JSON(更易读)
print("\n" + "=" * 50)
print("方法3:格式化的 JSON")
print("=" * 50)
# 使用 json.dumps 格式化输出
user_json_pretty = user.json(indent=2)
print(user_json_pretty)
# 方法4:从字典创建对象(反序列化)
print("\n" + "=" * 50)
print("方法4:从字典创建对象")
print("=" * 50)
# 假设我们从 API 或数据库获取了字典数据
data_from_api = {
"id": 2,
"name": "李四",
"email": "lisi@example.com",
"age": 30
}
# 使用 ** 解包字典创建对象
user_from_dict = User(**data_from_api)
print(f"从字典创建的对象: {user_from_dict}")
# 方法5:从 JSON 字符串创建对象
print("\n" + "=" * 50)
print("方法5:从 JSON 字符串创建对象")
print("=" * 50)
# 假设我们收到 JSON 字符串(比如从网络请求)
json_string = '{"id": 3, "name": "王五", "email": "wangwu@example.com", "age": 28}'
# parse_raw() 方法从 JSON 字符串创建对象
user_from_json = User.parse_raw(json_string)
print(f"从 JSON 创建的对象: {user_from_json}")
# 方法6:从文件读取 JSON 并创建对象
print("\n" + "=" * 50)
print("方法6:从文件读取(示例)")
print("=" * 50)
# 首先创建一个 JSON 文件
json_data = {"id": 4, "name": "赵六", "email": "zhaoliu@example.com", "age": 35}
with open("user_data.json", "w", encoding="utf-8") as f:
json.dump(json_data, f, ensure_ascii=False, indent=2)
print("已创建 user_data.json 文件")
# 从文件读取并创建对象
with open("user_data.json", "r", encoding="utf-8") as f:
data = json.load(f)
user_from_file = User(**data)
print(f"从文件创建的对象: {user_from_file}")
# 实际应用场景:API 数据交换
print("\n" + "=" * 50)
print("实际应用:模拟 API 数据交换")
print("=" * 50)
# 模拟:客户端发送数据到服务器
client_data = {"id": 5, "name": "孙七", "email": "sunqi@example.com"}
print(f"客户端发送: {client_data}")
# 服务器接收并验证
server_user = User(**client_data)
print(f"服务器验证通过: {server_user}")
# 服务器处理后将对象转换为 JSON 返回
response_json = server_user.json()
print(f"服务器返回 JSON: {response_json}")6.3 字段验证器 #
除了类型验证,我们还可以自定义验证规则。
创建文件 example_04_validator.py:
# ============================================
# 自定义字段验证器
# ============================================
from pydantic import BaseModel, validator, ValidationError
# 定义商品模型,包含自定义验证
class Product(BaseModel):
# 商品名称
name: str
# 商品价格
price: float
# 商品库存
stock: int
# 使用 @validator 装饰器定义价格验证器
# 'price' 表示验证 price 字段
@validator('price')
def price_must_be_positive(cls, v):
"""
验证价格必须大于 0
cls: 类本身(Product)
v: 字段的值(price 的值)
"""
# 如果价格小于等于 0,抛出错误
if v <= 0:
raise ValueError('价格必须大于 0')
# 如果验证通过,返回原值(也可以返回修改后的值)
return v
# 验证库存
@validator('stock')
def stock_must_be_non_negative(cls, v):
"""
验证库存不能为负数
"""
if v < 0:
raise ValueError('库存不能为负数')
return v
# 验证商品名称
@validator('name')
def name_must_not_be_empty(cls, v):
"""
验证商品名称不能为空
"""
# strip() 去除首尾空格
v = v.strip()
if not v: # 如果去除空格后为空
raise ValueError('商品名称不能为空')
return v # 返回去除空格后的值
# 示例1:正确的数据
print("=" * 50)
print("示例1:正确的数据")
print("=" * 50)
product1 = Product(name="笔记本电脑", price=5999.99, stock=10)
print(f"商品: {product1}")
print("验证通过!")
# 示例2:价格验证失败
print("\n" + "=" * 50)
print("示例2:价格验证失败")
print("=" * 50)
try:
# 价格是负数,验证会失败
product2 = Product(name="鼠标", price=-10.0, stock=5)
except ValidationError as e:
print("验证失败!")
for error in e.errors():
print(f" 字段: {error['loc']}")
print(f" 错误: {error['msg']}")
# 示例3:库存验证失败
print("\n" + "=" * 50)
print("示例3:库存验证失败")
print("=" * 50)
try:
# 库存是负数,验证会失败
product3 = Product(name="键盘", price=99.99, stock=-5)
except ValidationError as e:
print("验证失败!")
for error in e.errors():
print(f" 字段: {error['loc']}")
print(f" 错误: {error['msg']}")
# 示例4:名称验证(自动去除空格)
print("\n" + "=" * 50)
print("示例4:名称验证(自动处理)")
print("=" * 50)
# 名称前后有空格,验证器会自动去除
product4 = Product(name=" 显示器 ", price=1999.99, stock=8)
print(f"商品名称(已去除空格): '{product4.name}'")
# 示例5:多个验证器同时工作
print("\n" + "=" * 50)
print("示例5:多个验证器")
print("=" * 50)
try:
# 多个字段都有问题
product5 = Product(name="", price=-100, stock=-10)
except ValidationError as e:
print("验证失败!发现多个错误:")
for error in e.errors():
print(f" {error['loc']}: {error['msg']}")6.4 字段配置(Field) #
使用 Field 可以更灵活地配置字段。
创建文件 example_05_field.py:
# ============================================
# 字段配置(Field)
# ============================================
from pydantic import BaseModel, Field
from typing import Optional
# 定义商品模型,使用 Field 配置字段
class Item(BaseModel):
# Field 用于配置字段的详细属性
# ... 表示该字段是必需的(不能省略)
# min_length=1 表示最小长度为 1
# max_length=50 表示最大长度为 50
# description 是字段的描述信息
name: str = Field(..., min_length=1, max_length=50, description="商品名称")
# gt=0 表示必须大于 0(greater than)
# description 提供字段说明
price: float = Field(..., gt=0, description="商品价格,必须大于0")
# Optional[str] 表示可以是字符串或 None
# None 是默认值
# max_length=200 限制最大长度
description: Optional[str] = Field(None, max_length=200, description="商品描述")
# ge=0 表示大于等于 0(greater than or equal)
# le=100 表示小于等于 100(less than or equal)
# 10.0 是默认值
discount: float = Field(10.0, ge=0, le=100, description="折扣百分比,0-100之间")
# 示例1:正确的数据
print("=" * 50)
print("示例1:正确的数据")
print("=" * 50)
item1 = Item(name="笔记本电脑", price=5999.99, description="高性能游戏本", discount=15.0)
print(f"商品: {item1}")
# 示例2:使用默认值
print("\n" + "=" * 50)
print("示例2:使用默认值")
print("=" * 50)
item2 = Item(name="鼠标", price=29.99)
print(f"商品(使用默认折扣): {item2}")
# 示例3:字段长度验证
print("\n" + "=" * 50)
print("示例3:字段长度验证")
print("=" * 50)
from pydantic import ValidationError
try:
# 名称太长,超过 50 个字符
long_name = "a" * 51 # 创建 51 个字符的字符串
item3 = Item(name=long_name, price=99.99)
except ValidationError as e:
print("验证失败!")
for error in e.errors():
print(f" {error['loc']}: {error['msg']}")
# 示例4:数值范围验证
print("\n" + "=" * 50)
print("示例4:数值范围验证")
print("=" * 50)
try:
# 折扣超过 100,验证失败
item4 = Item(name="键盘", price=99.99, discount=150.0)
except ValidationError as e:
print("验证失败!")
for error in e.errors():
print(f" {error['loc']}: {error['msg']}")
# 示例5:查看字段信息
print("\n" + "=" * 50)
print("示例5:查看模型结构")
print("=" * 50)
# schema() 方法返回模型的完整结构信息
schema = Item.schema()
print("模型结构(JSON Schema):")
import json
print(json.dumps(schema, indent=2, ensure_ascii=False))6.5 嵌套模型 #
一个模型可以包含另一个模型,形成嵌套结构。
创建文件 example_06_nested.py:
# ============================================
# 嵌套模型
# ============================================
from pydantic import BaseModel
from typing import List, Optional
# 定义地址模型
class Address(BaseModel):
# 街道地址
street: str
# 城市
city: str
# 邮政编码
zip_code: str
# 定义公司模型,包含地址
class Company(BaseModel):
# 公司名称
name: str
# 公司地址,类型是 Address(嵌套模型)
address: Address
# 定义员工模型
class Employee(BaseModel):
# 员工姓名
name: str
# 员工年龄
age: int
# 所属公司(嵌套模型)
company: Company
# 技能列表(List[str] 表示字符串列表)
skills: List[str] = []
# 示例1:创建嵌套对象
print("=" * 50)
print("示例1:创建嵌套对象")
print("=" * 50)
# 方式1:先创建 Address 对象,再创建 Company 对象
address = Address(street="科技路123号", city="北京", zip_code="100000")
company = Company(name="ABC公司", address=address)
employee = Employee(name="张三", age=25, company=company, skills=["Python", "Java"])
print(f"员工: {employee}")
print(f"员工公司: {employee.company.name}")
print(f"公司地址: {employee.company.address.city}")
# 示例2:使用字典创建(更常用)
print("\n" + "=" * 50)
print("示例2:使用字典创建")
print("=" * 50)
# 可以直接用字典创建,Pydantic 会自动处理嵌套
employee_data = {
"name": "李四",
"age": 30,
"company": {
"name": "XYZ公司",
"address": {
"street": "创新路456号",
"city": "上海",
"zip_code": "200000"
}
},
"skills": ["JavaScript", "React", "Node.js"]
}
employee2 = Employee(**employee_data)
print(f"员工: {employee2}")
print(f"技能列表: {employee2.skills}")
# 示例3:嵌套验证
print("\n" + "=" * 50)
print("示例3:嵌套验证")
print("=" * 50)
from pydantic import ValidationError
try:
# 地址信息不完整(缺少 zip_code)
employee3 = Employee(
name="王五",
age=28,
company={
"name": "DEF公司",
"address": {
"street": "商业街789号",
"city": "广州"
# 缺少 zip_code
}
}
)
except ValidationError as e:
print("验证失败!嵌套模型验证出错:")
for error in e.errors():
print(f" {error['loc']}: {error['msg']}")
# 示例4:复杂嵌套(列表中的模型)
print("\n" + "=" * 50)
print("示例4:列表中的嵌套模型")
print("=" * 50)
# 定义订单项模型
class OrderItem(BaseModel):
product_name: str
quantity: int
price: float
# 定义订单模型,包含多个订单项
class Order(BaseModel):
order_id: int
customer_name: str
# List[OrderItem] 表示 OrderItem 对象的列表
items: List[OrderItem]
# 创建包含多个订单项的订单
order = Order(
order_id=1001,
customer_name="张三",
items=[
{"product_name": "笔记本电脑", "quantity": 1, "price": 5999.99},
{"product_name": "鼠标", "quantity": 2, "price": 29.99},
{"product_name": "键盘", "quantity": 1, "price": 99.99}
]
)
print(f"订单: {order}")
print(f"订单项数量: {len(order.items)}")
print(f"第一个订单项: {order.items[0].product_name}")7. 高级功能 #
7.1 可选字段和默认值 #
我们已经在前面的例子中看到了可选字段,这里再详细说明。
创建文件 example_07_optional.py:
# ============================================
# 可选字段和默认值
# ============================================
from pydantic import BaseModel, Field
from typing import Optional, List
# 定义用户模型,演示各种默认值用法
class UserProfile(BaseModel):
# 必需字段:没有默认值,创建对象时必须提供
username: str
email: str
# 可选字段1:使用 Optional 和 None 默认值
# 如果不提供,值就是 None
phone: Optional[str] = None
# 可选字段2:有具体的默认值
# 如果不提供,值就是 "普通用户"
role: str = "普通用户"
# 可选字段3:列表类型,默认是空列表
tags: List[str] = []
# 可选字段4:使用 Field 设置默认值和描述
age: int = Field(18, ge=0, le=150, description="用户年龄")
# 可选字段5:使用 Field 的 default_factory
# default_factory 用于生成默认值(每次创建对象时调用)
from datetime import datetime
created_at: datetime = Field(default_factory=datetime.now)
# 示例1:只提供必需字段
print("=" * 50)
print("示例1:只提供必需字段")
print("=" * 50)
user1 = UserProfile(username="张三", email="zhangsan@example.com")
print(f"用户: {user1}")
print(f"电话(默认 None): {user1.phone}")
print(f"角色(默认值): {user1.role}")
print(f"标签(默认空列表): {user1.tags}")
# 示例2:提供部分可选字段
print("\n" + "=" * 50)
print("示例2:提供部分可选字段")
print("=" * 50)
user2 = UserProfile(
username="李四",
email="lisi@example.com",
phone="13800138000",
role="管理员"
)
print(f"用户: {user2}")
# 示例3:修改列表默认值(注意陷阱)
print("\n" + "=" * 50)
print("示例3:列表默认值的陷阱")
print("=" * 50)
# ⚠️ 注意:不要直接修改列表默认值!
# 错误示例(不要这样做):
# tags: List[str] = [] # 这会导致所有对象共享同一个列表
# 正确做法:使用 Field 的 default_factory
class CorrectUser(BaseModel):
username: str
# 使用 default_factory,每次创建对象时都会创建新列表
tags: List[str] = Field(default_factory=list)
user3 = CorrectUser(username="王五")
user3.tags.append("VIP") # 修改这个对象的 tags
print(f"用户3的标签: {user3.tags}")
user4 = CorrectUser(username="赵六")
print(f"用户4的标签(应该是空的): {user4.tags}")7.2 数据转换和清洗 #
Pydantic 可以自动转换和清洗数据。
创建文件 example_08_transformation.py:
# ============================================
# 数据转换和清洗
# ============================================
from pydantic import BaseModel, validator
from typing import Optional
# 定义数据处理模型
class CleanedData(BaseModel):
# 用户ID
user_id: int
# 用户名(会自动去除首尾空格)
user_name: str
# 邮箱(转换为小写)
user_email: str
# 验证器:自动清洗用户名
@validator('user_name')
def clean_name(cls, v):
"""
清洗用户名:去除首尾空格,转换为标题格式
"""
# strip() 去除首尾空格
v = v.strip()
# title() 将首字母大写
v = v.title()
return v
# 验证器:自动清洗邮箱
@validator('user_email')
def clean_email(cls, v):
"""
清洗邮箱:转换为小写,去除空格
"""
# lower() 转换为小写
v = v.lower()
# replace() 去除空格
v = v.replace(" ", "")
return v
# 配置:忽略额外字段
class Config:
# extra = "ignore" 表示忽略模型中没有定义的字段
# 这样即使传入额外字段也不会报错
extra = "ignore"
# 示例1:自动清洗数据
print("=" * 50)
print("示例1:自动清洗数据")
print("=" * 50)
# 输入数据有空格、大小写混乱
raw_data = {
"user_id": 1,
"user_name": " zhang san ", # 有空格,小写
"user_email": " ZHANGSAN@EXAMPLE.COM " # 有空格,大写
}
cleaned = CleanedData(**raw_data)
print(f"原始数据: {raw_data}")
print(f"清洗后: {cleaned}")
print(f"用户名(已清洗): '{cleaned.user_name}'")
print(f"邮箱(已清洗): '{cleaned.user_email}'")
# 示例2:忽略额外字段
print("\n" + "=" * 50)
print("示例2:忽略额外字段")
print("=" * 50)
# 数据中包含模型中没有定义的字段
data_with_extra = {
"user_id": 2,
"user_name": "li si",
"user_email": "lisi@example.com",
"extra_field_1": "这个字段会被忽略",
"extra_field_2": 12345
}
cleaned2 = CleanedData(**data_with_extra)
print(f"包含额外字段的数据: {data_with_extra}")
print(f"处理后的对象(额外字段已忽略): {cleaned2}")
# 示例3:实际应用场景
print("\n" + "=" * 50)
print("示例3:实际应用场景")
print("=" * 50)
def process_user_data(raw_data: dict):
"""
处理用户数据的函数
自动验证和清洗数据
"""
try:
# 自动验证和清洗
cleaned_data = CleanedData(**raw_data)
print(f"✓ 数据验证通过")
print(f" 用户ID: {cleaned_data.user_id}")
print(f" 用户名: {cleaned_data.user_name}")
print(f" 邮箱: {cleaned_data.user_email}")
return cleaned_data
except Exception as e:
print(f"✗ 数据验证失败: {e}")
return None
# 测试处理函数
test_data = {
"user_id": "3", # 字符串,会自动转换为整数
"user_name": " wang wu ",
"user_email": "WANGWU@EXAMPLE.COM"
}
result = process_user_data(test_data)8. 实际应用场景 #
8.1 API 数据验证(FastAPI 示例) #
FastAPI 是一个现代 Python Web 框架,它内置支持 Pydantic。
创建文件 example_09_fastapi.py:
# ============================================
# FastAPI 中使用 Pydantic(示例)
# ============================================
# 注意:运行此示例需要安装 fastapi 和 uvicorn
# Windows: pip install fastapi uvicorn
# Mac: pip3 install fastapi uvicorn
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
from typing import Optional
# 创建 FastAPI 应用
app = FastAPI()
# 定义请求模型(客户端发送的数据)
class CreateUserRequest(BaseModel):
# 用户名
username: str
# 密码
password: str
# 邮箱(使用 EmailStr 确保格式正确)
email: EmailStr
# 年龄(可选)
age: Optional[int] = None
# 定义响应模型(服务器返回的数据)
class UserResponse(BaseModel):
# 用户ID
id: int
# 用户名
username: str
# 邮箱
email: str
# 年龄
age: Optional[int] = None
# 定义 API 端点
@app.post("/users/", response_model=UserResponse)
async def create_user(user: CreateUserRequest):
"""
创建用户的 API 端点
user 参数会自动使用 Pydantic 验证
"""
# 这里 user 已经是验证过的 CreateUserRequest 对象
# 可以直接使用,不需要手动验证
# 模拟创建用户(实际应该保存到数据库)
new_user = UserResponse(
id=1, # 实际应该从数据库获取
username=user.username,
email=user.email,
age=user.age
)
# FastAPI 会自动将 UserResponse 转换为 JSON 返回
return new_user
# 运行说明:
# 在终端运行: uvicorn example_09_fastapi:app --reload
# 然后访问: http://127.0.0.1:8000/docs 查看 API 文档
# 可以测试发送 POST 请求到 http://127.0.0.1:8000/users/
print("=" * 50)
print("FastAPI 示例代码")
print("=" * 50)
print("此文件定义了 FastAPI 应用和 Pydantic 模型")
print("要运行此示例,请执行以下步骤:")
print("1. 安装依赖: pip install fastapi uvicorn")
print("2. 运行服务器: uvicorn example_09_fastapi:app --reload")
print("3. 访问 http://127.0.0.1:8000/docs 查看 API 文档")8.2 配置文件管理 #
使用 Pydantic 管理应用配置。
创建文件 example_10_config.py:
# ============================================
# 配置文件管理
# ============================================
from pydantic import BaseSettings
from typing import Optional
# 定义配置模型
# BaseSettings 是 Pydantic 提供的特殊基类,用于管理配置
class Settings(BaseSettings):
# 应用名称,默认值是 "My App"
app_name: str = "My App"
# 数据库 URL(必需,没有默认值)
database_url: str
# 调试模式,默认是 False
debug: bool = False
# API 密钥(可选)
api_key: Optional[str] = None
# 最大连接数,默认是 10
max_connections: int = 10
# 配置类
class Config:
# 从 .env 文件加载环境变量
env_file = ".env"
# 环境变量文件编码
env_file_encoding = "utf-8"
# 创建配置对象
# 会自动从环境变量和 .env 文件加载配置
try:
settings = Settings()
print("=" * 50)
print("配置加载成功")
print("=" * 50)
print(f"应用名称: {settings.app_name}")
print(f"数据库 URL: {settings.database_url}")
print(f"调试模式: {settings.debug}")
print(f"最大连接数: {settings.max_connections}")
except Exception as e:
print("=" * 50)
print("配置加载失败")
print("=" * 50)
print(f"错误: {e}")
print("\n提示:")
print("1. 创建 .env 文件,内容如下:")
print(" database_url=postgresql://user:password@localhost/dbname")
print(" app_name=My Application")
print(" debug=true")
print("2. 或者设置环境变量:")
print(" Windows: set database_url=postgresql://...")
print(" Mac: export database_url=postgresql://...")
# 示例:手动创建配置(不使用环境变量)
print("\n" + "=" * 50)
print("手动创建配置示例")
print("=" * 50)
manual_settings = Settings(
app_name="手动配置的应用",
database_url="sqlite:///example.db",
debug=True,
max_connections=20
)
print(f"手动配置: {manual_settings}")创建 .env 文件(可选):
database_url=postgresql://user:password@localhost/mydb
app_name=My Application
debug=true
max_connections=208.3 数据验证和清洗工具 #
创建一个通用的数据验证工具。
创建文件 example_11_data_processor.py:
# ============================================
# 数据验证和清洗工具
# ============================================
from pydantic import BaseModel, validator, ValidationError
from typing import Optional, List, Dict, Any
# 定义用户数据模型
class UserData(BaseModel):
# 用户ID
user_id: int
# 用户名
user_name: str
# 用户邮箱
user_email: str
# 创建时间(可选)
created_at: Optional[str] = None
# 配置:忽略额外字段
class Config:
extra = "ignore"
# 验证用户名
@validator('user_name')
def validate_name(cls, v):
v = v.strip()
if not v:
raise ValueError('用户名不能为空')
if len(v) < 2:
raise ValueError('用户名至少2个字符')
return v
# 验证邮箱
@validator('user_email')
def validate_email(cls, v):
v = v.strip().lower()
if '@' not in v:
raise ValueError('邮箱格式不正确')
return v
# 数据处理器类
class DataProcessor:
"""
数据处理器,用于验证和清洗数据
"""
@staticmethod
def process_user_data(raw_data: dict) -> Dict[str, Any]:
"""
处理用户数据
参数:
raw_data: 原始数据字典
返回:
处理后的数据字典,包含 'success', 'data', 'errors' 字段
"""
try:
# 使用 Pydantic 自动验证和清洗
validated_data = UserData(**raw_data)
# 转换为字典返回
return {
"success": True,
"data": validated_data.dict(),
"errors": None
}
except ValidationError as e:
# 收集所有验证错误
errors = []
for error in e.errors():
errors.append({
"field": ".".join(str(x) for x in error["loc"]),
"message": error["msg"],
"type": error["type"]
})
return {
"success": False,
"data": None,
"errors": errors
}
except Exception as e:
return {
"success": False,
"data": None,
"errors": [{"message": str(e)}]
}
# 示例使用
print("=" * 50)
print("数据处理器示例")
print("=" * 50)
# 测试数据1:正确的数据
print("\n测试1:正确的数据")
test_data_1 = {
"user_id": 1,
"user_name": " 张三 ",
"user_email": " ZHANGSAN@EXAMPLE.COM "
}
result1 = DataProcessor.process_user_data(test_data_1)
print(f"成功: {result1['success']}")
if result1['success']:
print(f"处理后的数据: {result1['data']}")
else:
print(f"错误: {result1['errors']}")
# 测试数据2:错误的数据
print("\n测试2:错误的数据")
test_data_2 = {
"user_id": "不是数字",
"user_name": "A", # 太短
"user_email": "invalid-email" # 格式错误
}
result2 = DataProcessor.process_user_data(test_data_2)
print(f"成功: {result2['success']}")
if not result2['success']:
print("发现的错误:")
for error in result2['errors']:
print(f" - {error['field']}: {error['message']}")
# 测试数据3:包含额外字段
print("\n测试3:包含额外字段(会被忽略)")
test_data_3 = {
"user_id": 3,
"user_name": "李四",
"user_email": "lisi@example.com",
"extra_field_1": "这个会被忽略",
"extra_field_2": 12345
}
result3 = DataProcessor.process_user_data(test_data_3)
print(f"成功: {result3['success']}")
if result3['success']:
print(f"处理后的数据(额外字段已忽略): {result3['data']}")9. 常见问题 #
9.1 Q1: Pydantic 和普通 Python 类有什么区别? #
普通 Python 类:
class User:
def __init__(self, name: str, age: int):
self.name = name
self.age = age
# 不会验证类型
user = User(name=123, age="不是数字") # 不会报错,但类型错误Pydantic 模型:
from pydantic import BaseModel
class User(BaseModel):
name: str
age: int
# 自动验证类型
user = User(name=123, age="不是数字") # 会抛出 ValidationError9.2 Q2: 如何查看模型的字段信息? #
from pydantic import BaseModel
class User(BaseModel):
name: str
age: int = 18
# 查看模型结构
print(User.schema())
# 或者
print(User.schema_json(indent=2))9.3 Q3: 如何处理日期时间? #
from pydantic import BaseModel
from datetime import datetime
class Event(BaseModel):
name: str
# datetime 类型,Pydantic 会自动解析字符串
created_at: datetime
# 可以使用字符串创建
event = Event(name="会议", created_at="2024-01-01 12:00:00")
print(event.created_at) # 自动转换为 datetime 对象9.4 Q4: 如何只验证部分字段? #
from pydantic import BaseModel, ValidationError
class User(BaseModel):
name: str
age: int
email: str
# 使用 validate_assignment=False 可以部分更新
user = User(name="张三", age=25, email="zhangsan@example.com")
# 直接修改字段(默认会验证)
user.age = "不是数字" # 会抛出错误
# 如果需要部分验证,可以使用 validate() 方法9.5 Q5: Pydantic 性能如何? #
Pydantic 的性能很好,但对于大量数据的验证,可以考虑:
- 使用
validate_assignment=False关闭赋值验证 - 缓存验证结果
- 对于简单场景,可以考虑使用 dataclasses
10. 总结 #
Pydantic 是什么
- 基于类型注解的数据验证库
- 自动验证、转换和清洗数据
主要功能
- 类型验证:确保数据类型正确
- 自动转换:尝试将数据转换为正确类型
- 自定义验证:使用
@validator定义验证规则 - 序列化:轻松转换为字典或 JSON
适用场景
- API 数据验证(如 FastAPI)
- 配置文件管理
- 数据清洗和转换
- 任何需要验证数据的场景
优势
- 代码简洁:定义一次,自动验证
- 错误信息清晰:告诉你具体哪里错了
- 易于维护:修改模型定义即可
- 类型安全:基于 Python 类型提示