Python 写多了的人都有这个体会:动态类型爽是真爽,调试是真痛苦。一个函数传错参数,运行到第 50 步才报 AttributeError,回头排查半天。我以前的解法是写一堆 isinstance 判断,代码里全是「先检查再用」的样板。
后来用上 Pydantic 之后,这种代码我一行都不再写了。

一个例子看出 Pydantic 干啥用
不啰嗦,直接对比。
# 没有 Pydantic 的写法
def create_user(data: dict) -> dict:
if'name' not in data:
raiseValueError("name required")
ifnot isinstance(data['name'], str):
raiseTypeError("name must be str")
iflen(data['name']) < 2 or len(data['name']) > 30:
raiseValueError("name length 2-30")
if'email' not in data:
raiseValueError("email required")
if'@' not in data['email']:
raiseValueError("invalid email")
if'age' in data:
try:
age= int(data['age'])
except(ValueError, TypeError):
raiseValueError("age must be int")
ifage < 0 or age > 150:
raiseValueError("age 0-150")
else:
age= None
return{'name': data['name'], 'email': data['email'], 'age': age}
# Pydantic 写法
from pydantic import BaseModel, EmailStr, Field
from typing import Optional
class User(BaseModel):
name:str = Field(min_length=2, max_length=30)
email:EmailStr
age:Optional[int] = Field(default=None, ge=0, le=150)
def create_user(data: dict) -> User:
returnUser(**data)
# 调用
user = create_user({"name": "张三", "email": "z@example.com", "age": "28"})
# Pydantic 自动把 "28" 转成 int 28
# 任何字段不合规直接抛 ValidationError,错误信息精确到字段
print(user.name, user.email, user.age)
上面那一坨样板代码,下面 5 行模型替代。而且 Pydantic 还自动做了类型转换——传 "28" 字符串它自动转成 int 28,这种「合理强转」特别贴心。
Pydantic v2 我最常用的功能
from pydantic import BaseModel, Field, field_validator, model_validator, ConfigDict
from typing import List, Optional, Literal
from datetime import datetime
from decimal import Decimal
class OrderItem(BaseModel):
product_id:int = Field(gt=0, description="商品 ID")
quantity:int = Field(gt=0, le=999)
price:Decimal = Field(ge=Decimal('0.01'), max_digits=10, decimal_places=2)
class Order(BaseModel):
#配置:允许从 ORM 对象创建、严格模式等
model_config= ConfigDict(
from_attributes=True,#支持 Order.model_validate(orm_instance)
str_strip_whitespace=True,#自动去字符串两端空格
validate_assignment=True,#赋值时也校验
)
order_no:str = Field(pattern=r'^ORD\d{12}$')
status:Literal['PENDING', 'PAID', 'CANCELLED']
items:List[OrderItem] = Field(min_length=1, max_length=100)
created_at:datetime
remark:Optional[str] = Field(default=None, max_length=500)
#单字段自定义校验
@field_validator('order_no')
@classmethod
deforder_no_must_uppercase(cls, v: str) -> str:
ifnot v.isupper():
raiseValueError('订单号必须全大写')
returnv
#跨字段校验(用 model_validator,after 模式拿到的是已校验的对象)
@model_validator(mode='after')
defcheck_total(self):
ifself.status == 'PAID' and not self.items:
raiseValueError('已支付订单必须有商品')
returnself
#计算属性
@property
deftotal(self) -> Decimal:
returnsum(item.price * item.quantity for item in self.items)
# 用起来
order = Order.model_validate({
"order_no":"ORD202605271234",
"status":"PAID",
"items":[
{"product_id":1, "quantity": 2, "price": "99.50"},
],
"created_at":"2026-05-27T10:00:00",
})
print(order.total)# Decimal('199.00')
print(order.model_dump())# 转 dict
print(order.model_dump_json()) # 转 JSON 字符串
field_validator 用来检查单字段、清洗数据;model_validator(mode='after') 用来做跨字段校验。这两个加上 Field 的内置约束,90% 的校验需求都能搞定。
和 FastAPI 配合是真无敌
Pydantic 真正的杀手锏是和 FastAPI 联动。我现在写 Python 后端基本就这个组合。
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
from typing import List
app = FastAPI()
class CreateOrderRequest(BaseModel):
user_id:int = Field(gt=0)
items:List[dict] = Field(min_length=1)
remark:str = Field(default="", max_length=200)
class OrderResponse(BaseModel):
order_no:str
status:str
total:float
@app.post("/orders", response_model=OrderResponse)
def create_order(req: CreateOrderRequest):
#进到这里 req 已经是校验好的对象
#不合规的请求 FastAPI 自动返回 422 + 详细错误信息
#不用写一行 if 判断
order= save_order(req)
returnOrderResponse(
order_no=order.no,
status=order.status,
total=float(order.total)
)
# 还有彩蛋:FastAPI 会根据 Pydantic 模型自动生成 OpenAPI 文档
# 访问 /docs 就能看到完整的接口文档,能直接调试
接口校验、文档、类型提示,一套搞定。我以前用 Flask 时光写参数校验和文档就得花一半时间,现在这部分基本零成本。
几个新人容易踩的坑
# 坑 1:Pydantic 默认会强制类型转换,开 strict 模式才不会
class StrictModel(BaseModel):
model_config= ConfigDict(strict=True)
age:int
StrictModel(age="28")# 抛错,strict 模式下不接受字符串
# 不开 strict 的话:
class LooseModel(BaseModel):
age:int
LooseModel(age="28")# OK,自动转成 28
# 坑 2:可选字段必须用 Optional 或者给默认值
class Bad(BaseModel):
name:str = None# 类型不匹配,警告
class Good(BaseModel):
name:Optional[str] = None# 推荐
#或者 Python 3.10+
#name: str | None = None
# 坑 3:v1 和 v2 API 完全不一样
# v1: .dict()v2: .model_dump()
# v1: .json()v2: .model_dump_json()
# v1: .parse_obj()v2: .model_validate()
# v1: @validatorv2: @field_validator
# 网上搜到老代码不能直接抄
# 坑 4:性能。v2 比 v1 快很多(用 Rust 重写了核心),
# 但还是要注意 List[复杂模型] 在大批量时的开销
# 100w 条数据校验一次还是要几秒
我现在写 Python 项目的标配是:FastAPI + Pydantic + SQLAlchemy 2.0。数据从入口到 ORM 全程类型清晰,IDE 全部能跳转、能补全。再回头看以前用 dict 来回传的代码,自己都不敢碰。
类型这东西,写的时候多花两分钟,省的是后面无数次「这个 dict 里到底有啥」的脑细胞。