类型系统与 Pydantic 数据校验实战
Python 的类型提示(type hints)从可选的锦上添花,变成了现代 Python 项目的基础设施。而 Pydantic 把类型提示从"给人看的注释"变成了"运行时真正生效的数据校验"。本文从 Pydantic 模型定义讲起,覆盖字段校验、嵌套模型、序列化与 FastAPI 集成的完整实战。
一、为什么需要 Pydantic
Python 的类型提示(如 name: str)本身只是给人和 IDE 看的注释,运行时不做任何检查。你传入一个数字,解释器不会报错。Pydantic 解决了这个问题:它在运行时根据类型提示自动校验数据,类型不对就抛出清晰的错误信息。
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str
email: str
age: int = 0 # 默认值
# 正确数据
u = User(id=1, name="小明", email="xm@example.com")
print(u) # id=1 name='小明' email='xm@example.com' age=0
# 错误数据:类型不对会自动校验
try:
User(id="abc", name="小明", email="xm@example.com")
except Exception as e:
print(e)
# 会提示 id 应该是整数而不是字符串
pip install pydantic。Pydantic v2 用 Rust 重写了核心,速度比 v1 快 5-50 倍。FastAPI 底层就是基于 Pydantic 做请求体校验的。
二、字段校验与约束
Pydantic 不仅能校验类型,还能加字段约束:字符串长度、数值范围、正则匹配等。
from pydantic import BaseModel, Field, EmailStr
class User(BaseModel):
name: str = Field(min_length=2, max_length=20)
age: int = Field(ge=0, le=150) # 0 ≤ age ≤ 150
email: EmailStr # 自动校验邮箱格式
bio: str | None = Field(default=None, max_length=500)
# 测试
User(name="小明", age=25, email="xm@example.com") # OK
# User(name="A", age=200, email="bad-email") # 会报多个错误
Field() 提供了丰富的约束参数:ge(大于等于)、le(小于等于)、min_length、max_length、pattern(正则)等。
三、嵌套模型与复杂结构
真实业务数据往往是嵌套的。Pydantic 支持模型嵌套模型,自动递归校验。
from pydantic import BaseModel
class Address(BaseModel):
city: str
street: str
zipcode: str
class User(BaseModel):
name: str
email: str
address: Address # 嵌套模型
tags: list[str] = []
user = User(
name="小红",
email="xh@example.com",
address={"city": "佛山", "street": "季华六路", "zipcode": "528000"},
tags=["admin", "editor"]
)
print(user.address.city) # 佛山
注意 address 传入的是字典,Pydantic 自动把它转换成 Address 模型实例。这在处理 API 请求体时特别方便。
四、序列化与反序列化
Pydantic 模型可以轻松转换成字典或 JSON,也可以从 JSON 字符串创建模型实例。
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str
email: str
u = User(id=1, name="小明", email="xm@example.com")
# 转字典
print(u.model_dump())
# {'id': 1, 'name': '小明', 'email': 'xm@example.com'}
# 转 JSON
print(u.model_dump_json())
# {"id":1,"name":"小明","email":"xm@example.com"}
# 从字典创建
u2 = User.model_validate({"id": 2, "name": "小红", "email": "xh@example.com"})
# 从 JSON 字符串创建
u3 = User.model_validate_json('{"id":3,"name":"小刚","email":"xg@example.com"}')
五、自定义校验器
当内置约束不够用时,可以写自定义校验函数。
from pydantic import BaseModel, field_validator
class User(BaseModel):
name: str
email: str
password: str
@field_validator("name")
def name_must_not_be_admin(cls, v):
if v.lower() == "admin":
raise ValueError("不能使用 admin 作为用户名")
return v.strip() # 自动去掉首尾空格
@field_validator("password")
def password_strength(cls, v):
if len(v) < 8:
raise ValueError("密码至少 8 位")
if not any(c.isdigit() for c in v):
raise ValueError("密码必须包含数字")
return v
@field_validator 装饰的函数在字段赋值时自动执行,校验失败就抛出 ValueError,Pydantic 会把它包装成清晰的校验错误。
六、与 FastAPI 集成
FastAPI 直接用 Pydantic 模型做请求体定义,自动生成 OpenAPI 文档和校验错误。
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class UserCreate(BaseModel):
name: str
email: str
age: int = 0
@app.post("/users/")
def create_user(user: UserCreate):
# user 已经是校验过的 UserCreate 实例
return {"received": user.model_dump()}
发送一个缺少 email 的 POST 请求,FastAPI 会自动返回 422 状态码和详细的校验错误信息,不需要你手写任何校验代码。
七、总结
Pydantic 把 Python 的类型提示从文档级注释升级为运行时校验引擎。从简单的字段约束到嵌套模型递归校验,从自定义校验器到与 FastAPI 无缝集成,它是现代 Python Web 开发不可或缺的基础设施。记住:永远不要信任外部输入的数据,让 Pydantic 帮你做第一道防线。