pydantic教程🐧
Pydantic 是 Python 中使用最广泛的数据验证库。
快速且可扩展,Pydantic 与你的代码检查器/集成开发环境/大脑配合良好。以纯的、规范的 Python 3.8+ 定义数据应该如何;使用 Pydantic 对其进行验证。
pydantic的类型注解和fastapi可以说是一对苦命鸳鸯了,fastapi官方也很推崇使用pydantic!👍
一、安装
pip install pydantic二、基本使用
在 Pydantic 中定义模式的主要方法之一是通过模型。模型只是从 pydantic.BaseModel 继承并将字段定义为注释属性的类。
我们先来看一下一个例子,大致了解一下定义模型类的过程。
from app.schemas.base import Basefrom typing import Annotated, Optional, Listfrom datetime import datefrom pydantic import Fieldclass Frontmatter(Base): title:Annotated[str,Field()] description:Annotated[str,Field()] published:Annotated[Optional[date],Field(default_factory=date.today)]#利用default_factory会在实例化时调用后面的函数,而default是创建类就调用! draft:Annotated[Optional[bool],Field(default=False,description="是否为草稿")]#是否为草稿 tags:Annotated[Optional[List[str]],Field()] category:Annotated[Optional[str],Field()] pinned:Annotated[Optional[bool],Field(default=False)] licenseName:Annotated[Optional[str],Field(default=None)] image:Annotated[Optional[str],Field(default="./1.webp")]上面是定义模型类的一个例子,其中Base继承了BaseModel
这里还利用了typing的Annotated,这也是fastapi官方推崇的一种注解方式,当然你也可以不这么干😎
1.数据类型注解
from pydantic import BaseModel, Fieldfrom typing import Optional, List, Literal, Unionimport datetimeclass Fatdog(BaseModel): # 必选字段:基础类型 id: int # 可选字段(默认 None) email: Optional[str] = None #可以设置默认值 age: int = 20 # 枚举类型(仅限指定值) gender: Literal["male", "female", "other"] = "male" # 列表类型,default_factory只会在实例化时调用 fans: List[str] = Field(default_factory=list) # 日期类型 birthday: datetime.date = Field(default_factory=datetime.date.today)
# 尝试实例化,pydantic会try: dog = Fatdog( id="not_int", # id期望传入的是int!类型错误! gender="unknown", # 枚举值错误 extra_field="invalid" #这里传入了额外字段,但是不会出错,因为我们没配置model_config呢! #剩下的属性就懒得写啦!👌 )except Exception as e: print("验证错误:", e)上面的类型注解没什么好说的,你在其他语言里面也一定用过,现在我们聊一聊model_config吧!
2.model_config配置
v2的pydantic更推荐下面的写法
from pydantic import BaseModel, ConfigDict
class User(BaseModel): # ✅ 推荐写法:有类型提示,IDE 能自动补全 model_config = ConfigDict( extra='forbid', str_strip_whitespace=True )来看看ConfigDict的常用api吧!
️🤔1.extra:如何处理多余字段? (最重要!)
这个参数的作用防止你的憨批前端同志传入了一个额外字段,就如上面实例化fatdog的例子一样
| 值 | 含义 | 场景 | 例子 |
|---|---|---|---|
'forbid' | 禁止 (默认值🔥) | 生产环境推荐。防止前端传错参数,安全! | 传了 {"name": "A", "age": 18, "hobby": "x"},如果模型没定义 hobby,直接报错! |
'ignore' | 忽略 | 兼容旧接口,不在乎多余参数。 | 传了多余字段,直接扔掉,不报错也不保存。 |
'allow' | 允许 | 动态字段场景(如存储任意 KV 对)。 | 多余字段会被存到 model_fields_set 或特殊属性中 (V2 中需配合 kwargs 或特殊处理)。 |
胖狗建议:无脑选 'forbid'!除非你完全信任前端开发者,即使就是你自己🤪
🤔2.str_strip_whitespace:自动去空格 (最爽!)
值:布尔值 (True/False)。
True:所有字符串字段自动去除首尾空格。- 场景:用户表单提交
" ZhangSan ",自动变成"ZhangSan"。省得你每个字段都写.strip()!
🤔3.populate_by_name:字段别名兼容性 (前后端分离必备)
值:布尔值 (True/False)。
-
True:既可以通过原名赋值,也可以通过别名 (alias) 赋值。 -
场景
:
- 模型定义:
user_name: str = Field(alias="userName") - 如果设为
True:传{"user_name": "A"}✅ 和{"userName": "A"}✅ 都能成功。 - 如果设为
False(默认):只能传{"userName": "A"}(必须用别名)。
- 模型定义:
这个也很重要,毕竟前端喜欢驼峰式命名参数!
🤔4.from_attributes (原 orm_mode):对接数据库 ORM (操作数据库必用!)
可以把对象序列化成JSON的神器!!!
值:布尔值 (True/False)。
开启后 (True):Pydantic 变成了杂食动物。你给它字典 {“name”: “张三”} ✅,它吃;你给它对象 user_obj (它会自动去读 user_obj.name) ✅,它也吃!老吃家了哈哈哈!
-
True:允许从对象属性读取数据,而不仅仅是字典。 -
场景
FastAPI 返回数据库模型实例时。
- 数据库对象
db_user有db_user.name。 - 如果设为
True:User.model_validate(db_user)能成功。 - 如果设为
False:必须传字典{"name": "..."},传对象会报错。
- 数据库对象
-
注意:V1 叫
orm_mode = True,V2 改名叫from_attributes = True!别写错了!
#fastapi里的案例!@app.get("/users/{user_id}", response_model=UserResponse)def get_user(user_id: int): # 1. 从数据库查出来的是个对象 (db_user) db_user = session.query(DBUserModel).filter(...).first()
# 2. FastAPI 内部会自动调用 UserResponse.model_validate(db_user) # 如果没有 from_attributes=True,这里就会崩! return db_user流程如下数据库返回:DBUserModel 对象 (有 .name 属性)FastAPI 拦截返回值。FastAPI 看到 response_model=UserResponse。Pydantic 检查 UserResponse 配置。若 from_attributes=False ❌:报错,因为它期待字典。若 from_attributes=True ✅:Pydantic 自动执行 data = {"id": obj.id, "name": obj.name, ...},然后验证通过。最终返回给前端:{"id": 1, "name": "张三", ...} (纯 JSON)。🤔5.frozen:不可变模型 (线程安全)
值:布尔值 (True/False)。
True:模型实例化后,所有字段不可修改 (类似tuple)。- 场景:配置项、常量数据、多线程共享数据。
- 效果:尝试
user.name = "New"会直接报ValidationError。
🤔6.title / description / json_schema_extra:生成文档用
主要用于自动生成 OpenAPI/Swagger 文档的说明。
title: 模型的标题。json_schema_extra: 自定义 JSON Schema 的额外内容(比如添加示例example)。
如下,简直就是安全的配置!😎😎😎😎
from pydantic import BaseModel, ConfigDict
class UserCreate(BaseModel): """用户创建模型 (接收前端参数)""" model_config = ConfigDict( extra='forbid', # 1. 禁止多余字段,安全! str_strip_whitespace=True, # 2. 自动去空格,省心! populate_by_name=True, # 3. 兼容原名和别名,灵活! title="UserCreateSchema" # 4. Swagger 文档标题 )3.核心api
📤 第一组:【输出组】(把模型里的数据拿出来) 场景: 数据验证通过了,存在模型实例里了,现在要存数据库或者返回给前端。
model_dump() —— 人家是个转换器捏!😋
- 功能:把模型实例变成 Python 字典 (
dict)。 - 类比:就像把“精装书”拆成“散页纸”。
- 用途
- 存进 MongoDB。
- 传给其他只认字典的函数。
- FastAPI 默认就在后台偷偷用这个把对象转成 JSON。
user = User(name="张三", age=18)data = user.model_dump()# 结果:{'name': '张三', 'age': 18} (类型是 dict)model_dump_json() —— json起飞神器!
- 功能:直接把模型实例变成 JSON 字符串 (
str)。 - 类比:不仅拆成散页,还直接打包成“快递包裹”准备发货。
- 用途
- 直接写入 Redis。
- 写入日志文件。
- 手动通过 socket 发送数据。
- 注意:它返回的是字符串,不是字典!💢💢💢别特喵被坑了!
user = User(name="张三", age=18)json_str = user.model_dump_json()# 结果:'{"name": "张三", "age": 18}' (类型是 str,可以直接存文件)📥 第二组:【输入组】(把外面的数据塞进模型) 场景: 收到了前端请求、读到了配置文件、查到了数据库对象,要验证并创建模型实例。
model_validate() —— 最常用的接收器
- 功能:接收 字典 (
dict) 或 对象 (Object),验证后生成模型实例。 - 类比:安检口。不管你是坐飞机来的(字典)还是坐火车来的(ORM 对象),只要符合规定就放行。
- 关键点
- 如果配置了
from_attributes=True,它能直接吃 ORM 对象。 - 如果不配置,它主要吃字典。
- 如果配置了
# 情况 A: 传字典 (常规操作)data = {"name": "李四", "age": 20}user = User.model_validate(data)
# 情况 B: 传对象 (配合 from_attributes=True)db_obj = DBUser(name="王五", age=25)user = User.model_validate(db_obj)model_validate_json()——json字符串解码器
- 功能:接收 JSON 字符串 (
str),先解析成字典,再验证生成模型实例。 - 类比:专门处理“快递包裹”的安检口。先拆包(json.loads),再安检。
- 用途
- 从 Redis 读取了 JSON 字符串,想恢复成模型对象。
- 接收了微服务传来的 JSON 报文。
json_str = '{"name": "赵六", "age": 30}'user = User.model_validate_json(json_str)# 等价于:User.model_validate(json.loads(json_str))# 但写起来更爽,一步到位!🛠️ 第三组:【修改组】(在原有基础上动刀子) 场景: 对象已经创建好了,只想改其中一两个字段,不想重新 model_validate 一遍。
model_copy() —— *克隆机*
- 功能:复制当前模型,生成一个新的实例。
- 类比:影分身之术。
- 用途
- 你想基于现有数据创建一个新数据,只改一点点。
- 重要:默认是浅拷贝(Shallow Copy),如果里面有列表/字典,改新的会影响旧的!需要深拷贝得加参数
deep=True。
user1 = User(name="张三", age=18)
# 复制一个,顺便改个年龄user2 = user1.model_copy(update={"age": 19})
print(user1.age) # 18 (原身没变)print(user2.age) # 19 (分身变了)想“复制并修改”?直接用 model_copy(update={键:值})。这是最稳妥的套路!
终极奶龙搜查表
| 方法 | 输入是什么? | 输出是什么? | 一句话口诀 |
|---|---|---|---|
model_dump() | (无,调用者是自己) | Dict (字典) | 我要字典! (存库/处理) |
model_dump_json() | (无,调用者是自己) | Str (JSON 字符串) | 我要字符串! (存 Redis/日志) |
model_validate() | Dict 或 Object | Model 实例 | 给我数据(字典/对象),变模型! (接收请求/ORM) |
model_validate_json() | Str (JSON 字符串) | Model 实例 | 给我字符串,变模型! (读 Redis/报文) |
model_copy() | (可选 update 字典) | 新的 Model 实例 | 克隆我,顺便改点东西! (不可变数据处理) |
model_update() | (字典) | (通常返回 None 或自引用) | 别用了!用 model_copy(update=...) 代替! |
4.字段约束——Field
Field 是 Pydantic 定义字段约束的核心 API,支持自定义字段别名、描述、数值范围、长度限制等。
| 参数 | 作用 | 示例 |
|---|---|---|
alias | 字段别名(赋值/序列化时可使用) | email: str = Field(alias="user_email") |
gt/lt/ge/le | 数值约束(大于/小于/大于等于/小于等于) | age: int = Field(gt=0, le=120) |
min_length/max_length | 字符串长度约束 | username: str = Field(min_length=3, max_length=20) |
description | 字段描述(生成 JSON Schema 时用) | id: int = Field(description="用户唯一ID") |
default_factory | 动态默认值(避免可变默认值陷阱) | hobbies: List[str] = Field(default_factory=list) |
pattern | 正则表达式约束(字符串字段) | phone: str = Field(pattern=r"^1[3-9]\d{9}$") |
比如:
class Dog(BaseModel): height: float = Field(ge=0.5, le=2.5, description="身高(米):0.5-2.5") # 别名示例 user_email: str = Field(alias="email", description="用户邮箱")
model_config = {"populate_by_name": True} # 支持别名/原名赋值三、自定义验证:field_validator(核心扩展 API)
当内置约束无法满足需求时,使用 @field_validator 自定义字段验证逻辑(v2 替代 v1 的 @validator)。
它的核心作用就一个:在数据进入模型之前,或者在模型初始化时,对特定字段进行“特殊体检”!
如果 int, str, email 这些内置类型满足不了你,比如你要检查“密码必须包含大写”、“年龄不能是负数且不能超过 150”,这时候就得请出 field_validator!
基本写法🙌
from pydantic import BaseModel, field_validator
class User(BaseModel): name: str age: int
# 🔥 语法格式: @field_validator('字段名') # 可以传多个字段:@field_validator('age', 'score') def check_age(cls, v): # v 是字段的原始值 # 这里写你的逻辑 if v < 0: raise ValueError('年龄不能是负数!') # 报错必须抛 ValueError return v # ⚠️ 重要:必须返回处理后的值!关键参数
*fields (必填)
你要验证哪个字段?
@field_validator('name'):只验证name。@field_validator('name', 'nickname'):同一个逻辑验证两个字段。
mode **(模式:什么时候验?很七八重要,别问为什么🤡) **
决定你的函数是在“原始数据”上跑,还是在“转换后数据”上跑。
| 模式 | 含义 | 输入值 v 的类型 | 场景举例 |
|---|---|---|---|
'before' | 先验后转 | 原始输入 (可能是 str, dict, 任何鬼东西) | 想把 " 18 " 变成 18 再给 Pydantic 转 int;或者解析复杂的自定义字符串格式。 |
'after' (默认🔥) | 先转后验 | 已转换类型 (已经是 int, datetime 等) | 检查 age 是否大于 0;检查 email 格式是否符合公司规定。95% 的场景用这个! |
'plain' | 只验不转 | 原始输入 | 完全接管该字段的验证和转换逻辑(高级用法,类似 Annotated)。 |
'wrap' | 包围模式 | 原始输入 + 处理函数 | 想在验证前后都加点逻辑(比如记录日志),或者手动调用默认验证器。 |
如果你只是做逻辑判断(如:不能为负、长度限制),不用写 mode,默认就是 ‘after’,此时 v 已经是正确的类型了,爽歪歪! 如果你需要预处理数据(如:把 “2023/01/01” 这种奇怪格式转成日期),才用 mode=‘before’。)
经典密码校验
from pydantic import BaseModel, field_validator
class User(BaseModel): username: str password: str
@field_validator('password') def password_must_be_strong(cls, v): if len(v) < 8: raise ValueError('密码太短了,至少 8 位!') if not any(c.isupper() for c in v): raise ValueError('密码必须包含大写字母!') return v # 记得返回!
# 测试try: User(username="admin", password="123") # ❌ 报错:太短except Exception as e: print(e)
try: User(username="admin", password="weakpass") # ❌ 报错:无大写except Exception as e: print(e)
u = User(username="admin", password="StrongPass123") # ✅ 成功预处理日期
from pydantic import BaseModel, field_validatorfrom datetime import datetime
class Event(BaseModel): name: str event_date: datetime # 目标是 datetime 对象
@field_validator('event_date', mode='before') def parse_weird_date(cls, v): if isinstance(v, str): # 尝试把 "/" 替换成 "-" v = v.replace('/', '-') # 也可以在这里做更复杂的正则解析 print(f"正在预处理日期:{v}") return v # 返回处理后的字符串,让 Pydantic 继续转成 datetime
e = Event(name="年会", event_date="2026/03/21")# 输出:正在预处理日期:2026-03-21# 结果:event_date 成功变成了 datetime 对象总结
写不动了😭pydantic的内容很多,这里只是核心常用的几个api,如果你需要深入理解,可以访问
最近容易感冒,我已经中招了,别感冒,大佬!😎

部分信息可能已经过时









