类型系统与 Pydantic 数据校验实战

Python 的类型提示(type hints)从可选的锦上添花,变成了现代 Python 项目的基础设施。而 Pydantic 把类型提示从"给人看的注释"变成了"运行时真正生效的数据校验"。本文从 Pydantic 模型定义讲起,覆盖字段校验、嵌套模型、序列化与 FastAPI 集成的完整实战。

Pydantic 数据校验配图:蓝色渐变背景上盾牌形过滤关卡串联数据流,扁平科技插画
Pydantic:类型提示在运行时变成真正的数据校验关卡

一、为什么需要 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 帮你做第一道防线。