当前位置:首页>python>Pydantic 用得好,Python 也能写出强类型代码

Pydantic 用得好,Python 也能写出强类型代码

  • 2026-09-07 19:38:20
Pydantic 用得好,Python 也能写出强类型代码

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 里到底有啥」的脑细胞。

最新文章

随机文章