mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6
2652 字
7 分钟
pydantic教程
2026-03-21

pydantic教程🐧#

Pydantic 是 Python 中使用最广泛的数据验证库。

快速且可扩展,Pydantic 与你的代码检查器/集成开发环境/大脑配合良好。以纯的、规范的 Python 3.8+ 定义数据应该如何;使用 Pydantic 对其进行验证。

pydantic的类型注解和fastapi可以说是一对苦命鸳鸯了,fastapi官方也很推崇使用pydantic!👍

一、安装#

pip install pydantic

二、基本使用#

在 Pydantic 中定义模式的主要方法之一是通过模型。模型只是从 pydantic.BaseModel 继承并将字段定义为注释属性的类。

我们先来看一下一个例子,大致了解一下定义模型类的过程。

from app.schemas.base import Base
from typing import Annotated, Optional, List
from datetime import date
from pydantic import Field
class 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, Field
from typing import Optional, List, Literal, Union
import datetime
class 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_userdb_user.name
    • 如果设为 TrueUser.model_validate(db_user) 能成功。
    • 如果设为 False:必须传字典 {"name": "..."},传对象会报错。
  • 注意:V1 叫 orm_mode = TrueV2 改名叫 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 或 ObjectModel 实例给我数据(字典/对象),变模型! (接收请求/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_validator
from 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,如果你需要深入理解,可以访问

欢迎使用 Pydantic - Pydantic 官方文档

最近容易感冒,我已经中招了,别感冒,大佬!😎

112

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

pydantic教程
https://mizuki.mysqil.com/posts/pydantic/
作者
神秘大胖狗
发布于
2026-03-21
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

封面
Sample Song
Sample Artist
封面
Sample Song
Sample Artist
0:00 / 0:00