Pydantic 实战指南:
Python 数据验证与建模的最佳拍档
还在用一堆 if-else 手写数据校验?一文带你用 Pydantic 把"裸奔"的数据变成"铁布衫"。
一、为什么你需要 Pydantic?
写 Python 久了你会发现,真正的 bug 往往不是算法错了,而是数据"长歪了":接口返回的字段突然缺失、用户输入的年龄是字符串 "abc"、配置文件里多了一个你从没见过的键。传统的防御方式是满屏的 if not isinstance(x, int) 和 try/except,又丑又容易漏。
Pydantic 的核心思想极其优雅:用 Python 的类型注解(type hints)直接定义"数据应该长什么样",剩下的校验、转换、报错全交给它。它基于类型注解在运行时做数据校验,性能好、报错清晰、和现代 Python 生态(尤其是 FastAPI)无缝衔接。
📌 一句话理解:Pydantic 就是给你的数据套上一层"安检门"——不合规的数据进不来,进来的数据自动被收拾成你想要的模样。
它不发明新语法,完全复用 Python 原生的类型注解,学习成本极低,但收益立竿见影。
二、5 分钟上手:定义你的第一个模型
一切从 BaseModel 开始。下面就是一个最朴素的用户模型:
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str
email: str
age: int = 18 # 带默认值,不传也能创建
# 直接传 dict,Pydantic 自动校验并实例化
u = User(id=1, name="小明", email="xiao@example.com")
print(u.name) # 小明
print(u.age) # 18(用了默认值)
# 神奇之处:它还会"顺手"做类型转换
u2 = User(id="2", name="小红", email="hong@x.com", age="20")
print(u2.id, type(u2.id)) # 2 <class 'int'> 字符串被转成了 int
print(u2.age, type(u2.age)) # 20 <class 'int'>
注意最后一段:你明明传了字符串 "2" 和 "20",Pydantic 在不损失信息的前提下自动帮你转换成了 int。这正是它比手写校验省心的地方。
💡 新手提示:Pydantic 默认是"宽松校验"——能转就转。如果你希望严格禁止类型转换(比如禁止字符串转 int),可以用 model_config = ConfigDict(strict=True)(V2)或 Config.strict = True(V1)。
三、字段约束:让数据"有规矩"
光有类型还不够,真实业务里你得限制:名字不能为空、年龄在合理范围、邮箱得是合法格式。Field 就是干这个的:
from pydantic import BaseModel, Field, EmailStr
class User(BaseModel):
name: str = Field(..., min_length=1, max_length=50)
age: int = Field(..., ge=0, le=120) # greater-equal 0, less-equal 120
email: EmailStr # 专门的邮箱类型,自动校验格式
score: float = Field(0.0, ge=0.0, le=100.0)
# 试试不合法的数据
User(name="", age=200, email="not-an-email")
# pydantic_core._pydantic_core.ValidationError:
# 1. name: String should have at least 1 character
# 2. age: Input should be less than or equal to 120
# 3. email: value is not a valid email address
常用约束速查:
• ...:表示"必填,无默认值"(Ellipsis,三点是 Python 的省略号字面量)。
• ge / gt / le / lt:大于等于 / 严格大于 / 小于等于 / 严格小于,用于数字范围。
• min_length / max_length:字符串长度限制。
• EmailStr、AnyUrl、conint、constr 等:内置与构造类型,开箱即用。
四、嵌套模型与复杂结构
真实数据总是层层嵌套的。Pydantic 天然支持模型套模型,以及 list、dict、tuple 等容器,校验会递归深入到每一层:
from typing import List
from pydantic import BaseModel, EmailStr
class Address(BaseModel):
city: str
street: str
zipcode: str
class User(BaseModel):
name: str
email: EmailStr
addresses: List[Address] # 嵌套列表,每项都会按 Address 校验
tags: dict[str, str] = {} # 字典也行
data = {
"name": "小明",
"email": "xiao@x.com",
"addresses": [
{"city": "北京", "street": "朝阳路", "zipcode": "100020"},
{"city": "上海", "street": "南京路", "zipcode": "200001"},
],
}
u = User(**data)
print(u.addresses[0].city) # 北京
print(type(u.addresses[0])) # <class '__main__.Address'>
看到没?addresses 里的每个字典都被自动变成了 Address 实例,后面可以点属性访问,再也不用手动 dict["key"] 写一堆又易错的代码。
五、解析与转换:进出都省心
模型建好之后,最常做的就是把数据"装进去"和"倒出来"。Pydantic 提供了一组对称的方法:
# 从 dict 创建(V2 推荐 model_validate,V1 用 parse_obj)
u = User.model_validate({"name": "小红", "email": "h@x.com"})
# 从 JSON 字符串创建
u2 = User.model_validate_json('{"name": "小刚", "email": "g@x.com"}')
# 转回 dict / JSON
d = u.model_dump() # -> {'name': '小红', 'email': 'h@x.com', ...}
j = u.model_dump_json() # -> '{"name":"小红","email":"h@x.com",...}'
# 排除/包含特定字段也很容易
print(u.model_dump(include={"name"})) # {'name': '小红'}
V1 vs V2 命名提醒:老版本(V1)用 parse_obj / parse_raw;新版本(V2)统一为 model_validate / model_validate_json,语义更清晰。新项目请直接用 V2。
六、实战:配合 FastAPI 做接口校验
还记得我们之前写过的 FastAPI 吗?它正是靠 Pydantic 实现了"声明即校验"。下面这个接口,请求体自动按 Item 模型校验,连 Swagger 文档都自动生成:
from fastapi import FastAPI
from pydantic import BaseModel, Field
app = FastAPI()
class Item(BaseModel):
name: str = Field(..., min_length=1)
price: float = Field(..., gt=0)
in_stock: bool = True
@app.post("/items/")
def create_item(item: Item):
# item 已经是校验通过、类型正确的对象
return {"msg": f"收到 {item.name},单价 {item.price}"}
# 用错误数据 POST 会自动得到 422 校验错误,无需你写一行校验代码
这就是 Pydantic 的"降维打击"——把校验逻辑从业务代码里彻底剥离,你只管声明规则,框架帮你兜住所有脏数据。
七、配置项 model_config:灵活控制行为
Pydantic 的 ConfigDict(V2)能精细控制模型行为。下面三个是工程里最高频的:
from pydantic import BaseModel, ConfigDict
class User(BaseModel):
model_config = ConfigDict(
from_attributes=True, # 允许从 ORM 对象 / 带属性的对象加载
extra="forbid", # 禁止多余字段,传了不存在的键直接报错
populate_by_name=True, # 允许用别名或原名填充
)
name: str
age: int
extra 三态:
• "ignore"(默认):多余字段直接忽略。
• "forbid":多余字段 → 抛错(适合严格接口契约)。
• "allow":多余字段被保留在 model.extra 里。
八、校验器:field_validator 与 model_validator
当默认约束不够用时,用校验器写自定义逻辑。V2 用 @field_validator(单字段)和 @model_validator(跨字段):
from pydantic import BaseModel, field_validator, model_validator
class Register(BaseModel):
username: str
password: str
confirm: str
@field_validator("username")
@classmethod
def username_must_lower(cls, v):
# 统一转小写,去除首尾空格
return v.strip().lower()
@model_validator(mode="after")
def passwords_match(self):
# 跨字段校验:两次密码必须一致
if self.password != self.confirm:
raise ValueError("两次输入的密码不一致")
return self
# Register(username=" ADMIN ", password="abc", confirm="abc")
# -> username 会变成 "admin",且密码一致校验通过
校验器是"数据清洗 + 规则校验"的完美结合点:你可以在这里做归一化(去空格、转大小写)、跨字段一致性检查、甚至调用外部服务做唯一性校验。
⚠️ 版本注意:V1 用 @validator 和 @root_validator;V2 已更名为 @field_validator 和 @model_validator。老代码迁移时别用混。
九、错误处理:优雅捕获 ValidationError
校验失败会抛出 ValidationError。不要只 print,要结构化地取出每一项错误,返回给前端或写入日志:
from pydantic import BaseModel, ValidationError, Field
class User(BaseModel):
name: str = Field(..., min_length=2)
age: int = Field(..., ge=0)
try:
User(name="A", age=-5)
except ValidationError as e:
# e.errors() 返回结构化的错误列表
for err in e.errors():
print(err["loc"], "->", err["msg"], "->", err["type"])
# ('name',) -> String should have at least 2 characters -> string_too_short
# ('age',) -> Input should be greater than or equal to 0 -> greater_than_equal
e.errors() 给出的 loc / msg / type 非常适合直接序列化后返回给接口调用方,做精细化错误提示。
十、Pydantic V2:Rust 内核,快到飞起
2023 年发布的 V2 是 Pydantic 的里程碑:核心校验引擎用 Rust(pydantic-core)重写,在多数场景下比 V1 快 5~50 倍。除了更快,V2 还带来更现代的用法:
from typing import Annotated
from pydantic import BaseModel, Field, TypeAdapter
# 用 Annotated 把约束"贴"在类型上,复用性更强
PositiveInt = Annotated[int, Field(gt=0)]
NameStr = Annotated[str, Field(min_length=1, max_length=30)]
class Product(BaseModel):
id: PositiveInt
name: NameStr
# TypeAdapter:给"非模型"的零散类型也加上校验能力
adapter = TypeAdapter(list[PositiveInt])
print(adapter.validate_python([1, 2, "3"])) # [1, 2, 3]
工程建议:新项目一律上 V2;老项目可用官方 pydantic-migrate 辅助迁移。TypeAdapter 是 V2 的隐藏神器,单值、列表、联合类型都能校验,不必硬套 BaseModel。
十一、裸写校验 vs Pydantic:一图看懂
| 维度 |
手写 if-else |
Pydantic |
| 代码量 |
多且重复 |
少,声明式 |
| 类型转换 |
需手动写 |
自动 |
| 错误提示 |
笼统 |
结构化、定位精确 |
| 嵌套支持 |
要递归手写 |
原生递归 |
| 性能 |
一般 |
Rust 内核,极快 |
十二、总结:Pydantic 学习路线图
✅ 先掌握:BaseModel、类型注解、Field 约束、model_dump / model_validate。
✅ 再进阶:嵌套模型、field_validator / model_validator、model_config、ValidationError。
✅ 最后实战:接入 FastAPI 接口校验、ORM 读取(from_attributes)、TypeAdapter 校验零散类型。
一句话总结:Pydantic 把"数据长什么样"这件事,从散落在各处的隐式约定,变成了一行行可读、可复用、可校验的代码。它让你把精力从"防脏数据"转移到真正创造价值的业务逻辑上。
🚀 立即行动
pip install pydantic → 复制上面的第一个模型 → 喂一段脏数据进去 → 看它如何把数据收拾干净。
Pydantic 官方文档:https://docs.pydantic.dev(含中文指南)
— END —
关注我们,每周一个 Python 热门技术实战教程