强烈推荐一个 Python 神库——Pydantic
off999 2025-05-25 14:50 28 浏览 0 评论
初识 Pydantic
当我第一次接触 FastAPI 时,不可避免地遇到了 Pydantic。在 FastAPI 的生态中,Pydantic 几乎是不可或缺的——它负责请求/响应数据的校验、序列化和转换。然而,我的初次体验并不顺利:它的学习曲线略显陡峭,而且似乎提供了多种方式来完成同一件事,却没有明确的“最佳实践”指引。
尽管如此,随着深入使用,我逐渐意识到 Pydantic 的强大之处。它不仅仅是一个数据校验工具,更是一个能极大提升代码健壮性和可维护性的库。如今,它已成为我最喜爱的 十大 Python 库 之一。
版本说明与注意事项
在深入探讨之前,有两点需要特别注意:
- 本文基于 Pydantic v2:Pydantic v1 和 v2 之间存在显著差异,许多旧版代码在新版本中已不适用。
- 谨慎使用 AI 辅助工具(如 ChatGPT/Gemini):它们给出的示例可能是 v1 和 v2 的混合体,容易导致兼容性问题。
Pydantic 是什么?
Pydantic 是一个基于 Python 类型注解的 数据验证与解析库,其核心功能包括:
- 数据校验(Validation):确保输入数据符合预期结构(如类型检查、必填字段、取值范围等)。
- 数据转换(Parsing & Conversion):自动将原始数据(如 JSON、字典)转换为 Python 对象,并支持自定义转换逻辑。
- 序列化(Serialization):将 Python 对象转换为 JSON、字典等格式,便于 API 传输或存储。
为什么选用 Pydantic?
- 与 Python 类型系统深度集成:利用 typing 模块(如 str, int, List, Optional)定义数据Model,减少样板代码。
- 运行时数据安全保障:自动校验数据,避免无效或恶意输入进入业务逻辑。
- 无缝集成 FastAPI、SQLAlchemy 等框架:在 Web API、数据库交互等场景下提供高效的数据处理能力。
在接下来的章节中,我们将通过实际示例深入探讨 Pydantic 的核心功能,帮助你掌握这一强大工具。
一个非常基本的例子
假设你有一个需要名字和姓氏的函数。你需要确保两者都存在并且它们是字符串。
from pydantic import BaseModel
class MyFirstModel(BaseModel):
first_name: str
last_name: str
validating = MyFirstModel(first_name="marc", last_name="nealer")虽然这个例子有点傻,但它表明了几点。首先,你可以看到 Pydantic 类看起来几乎与 Python 数据类相同。其次要注意的是,与数据类不同,Pydantic 会检查值是否为字符串,如果不是,则会发出验证错误。
需要注意的是,像这里展示的 give 类型验证被称为默认验证。稍后我们将讨论在此之前和之后的验证。
更复杂一点
当涉及可选参数时,Pydantic 可以毫无问题地处理,但输入可能不符合你的预期
from pydantic import BaseModel
from typing import Union, Optional
classMySecondModel(BaseModel):
first_name: str
middle_name: Union[str, None] # 这意味着参数不必发送
title: Optional [str] # 这意味着参数应该发送,但可以为 None
last_name: str因此,如果你使用 Union,并且选项为 None,那么无论参数是否存在,Pydantic 都可以接受。如果你使用 Optional[],它期望发送参数,即使参数为空。这种表示法可能符合你的预期,但我觉得有点奇怪。
由此,你可以看到我们可以使用类型库中的所有对象,并且 Pydantic 将对它们进行验证。
from pydantic import BaseModel
from typing import Union, List, Dict
from datetime import datetime
class MyThirdModel(BaseModel):
name: Dict[str: str]
skills: List[str]
holidays: List[Union[str, datetime]]应用默认值
到目前为止,我们还没有讨论如果价值观缺失我们该怎么办。
from pydantic import BaseModel
class DefaultsModel(BaseModel):
first_name: str = "jane"
middle_names: list = []
last_name : str = "doe"这看起来似乎很明显。然而,有一个问题,那就是列表的定义。如果你以这种方式编写Model,则只会创建一个列表对象,并且该对象在该Model的所有实例之间共享。字典等也是如此。
为了解决这个问题,我们需要引入 Field 对象。
from pydantic import BaseModel, Field
class DefaultsModel(BaseModel):
first_name: str = "jane"
middle_names: list = Field(default_factory=list)
last_name: str = "doe"
请注意,传递给默认工厂的是类或函数,而不是其实例。这会导致为Model的所有实例创建一个新实例。
如果你看过 Pydantic 的文档,就会发现 Field 类有很多不同的用法。然而,我使用 Pydantic 越多,就越少用到 Field 对象。它可以做很多事情,但也会让事情变得复杂。对于默认值和默认工厂来说,它是可行的。至于其他的,你会看到我在这里做了什么。
嵌套Model
我不太需要使用嵌套的 Pydantic Model,但我认为它很有用。嵌套非常简单
from pydantic import BaseModel
class NameModel(BaseModel):
first_name: str
last_name: str
class UserModel(BaseModel):
username: str
name: NameModel自定义验证
虽然默认的类型验证已经很棒了,但我们总是需要超越它。Pydantic 提供了多种不同的方式,你可以添加自己的验证例程。
在开始研究这些之前,我们需要先讨论一下 Before 和 After 选项。正如我上面所说,绑定验证被视为默认验证,因此当 Pydantic 在字段上添加自定义验证时,它被定义为在此默认验证之前或之后。
对于我们稍后将讨论的Model验证,其含义有所不同。“之前”是指在对象初始化之前进行验证;“之后”是指在对象初始化完成后,其他验证也已完成。
字段验证
我们可以使用 Field() 对象定义验证,但随着我们对 Pydantic 的深入了解,过度使用 Field() 对象会让事情变得困难。我们也可以使用装饰器创建验证器,并声明它应该应用于哪些字段。我更喜欢使用带注释的验证器。它们简洁明了,易于理解。其他程序员可以轻松地理解你的操作。
from pydantic import BaseModel, BeforeValidator, ValidationError
import datetime
from typing import Annotated
def stamp2date(value):
if not isinstance(value, float):
raise ValidationError("incoming date must be a timestamp")
try:
res = datetime.datetime.fromtimestamp(value)
except ValueError:
raise ValidationError("Time stamp appears to be invalid")
return res
class DateModel(BaseModel):
dob: Annotated[datetime.datetime, BeforeValidator(stamp2date)]本示例在默认验证之前验证数据。这非常有用,因为它让我们有机会更改和重新格式化数据以及进行验证。在本例中,我期望传递一个数值时间戳。我验证了这一点,然后将时间戳转换为 datetime 对象。默认验证期望的是 datetime 对象。
Pydantic 还提供了 AfterValidator 和 WrapValidator。前者在默认验证器之后运行,后者则像中间件一样,在验证器之前和之后执行操作。我们还可以应用多个验证器
from pydantic import BaseModel, BeforeValidator, AfterValidator, ValidationError
import datetime
from typing import Annotated
defone_year(value):
if value < datetime.datetime.today() - datetime.timedelta(days=365):
raise ValidationError("the date must be less than a year old")
return value
defstamp2date(value):
ifnot isinstance(value, float):
raise ValidationError("incoming date must be a timestamp")
try:
res = datetime.datetime.fromtimestamp(value)
except ValueError:
raise ValidationError("Time stamp appears to be invalid")
return res
classDateModel(BaseModel):
dob: Annotated[datetime.datetime, BeforeValidator(stamp2date), AfterValidator(one_year)]大多数情况下,我使用 BeforeValidator。在很多情况下,转换传入的数据是必须的。当你想检查值的类型是否正确时,AfterValidator 非常有用。我还没用过 WrapValidator。我想听听用过的人的意见,因为我想了解它的用例。
在继续之前,我想举一个例子来说明多种类型需要可选的情况。或者更确切地说,参数是可选的。
from pydantic import BaseModel, BeforeValidator, ValidationError, Field
import datetime
from typing import Annotated
defstamp2date(value):
ifnot isinstance(value, float):
raise ValidationError("incoming date must be a timestamp")
try:
res = datetime.datetime.fromtimestamp(value)
except ValueError:
raise ValidationError("Time stamp appears to be invalid")
return res
classDateModel(BaseModel):
dob: Annotated[Annotated[datetime.datetime, BeforeValidator(stamp2date)] | None, Field(default=None)]Model验证
来看一个简单的用例。我们有三个值,它们都是可选的,但至少必须发送其中一个。字段验证只会检查每个字段本身,所以在这里不太适用。这时,Model验证就派上用场了。
from pydantic import BaseModel, model_validator, ValidationError
from typing import Union, Any
classAllOptionalAfterModel(BaseModel):
param1: Union[str, None] = None
param2: Union[str, None] = None
param3: Union[str, None] = None
@model_validator(mode="after")
defthere_must_be_one(self):
ifnot (self.param1 or self.param2 or self.param3):
raise ValidationError("One parameter must be specified")
return self
classAllOptionalBeforeModel(BaseModel):
param1: Union[str, None] = None
param2: Union[str, None] = None
param3: Union[str, None] = None
@model_validator(mode="before")
@classmethod
defthere_must_be_one(cls, data: Any):
ifnot (data["param1"] or data["param2"] or data["param3"]):
raise ValidationError("One parameter must be specified")
return data以上是两个示例。第一个是 After 验证。你会注意到它标记了 mode="after",并且将对象作为 self 传递。这是一个重要的区别。
Before 验证的流程截然不同。首先,你会看到带有 mode="before" 的 model_validation 装饰器。然后是 classmethod 装饰器。重要提示:你需要按此顺序同时指定 和 。
当我没有这样做时,我收到了一些非常奇怪的错误消息,因此这是需要注意的重要一点。
接下来你会注意到,类和传递给类的数据(参数)都作为参数传递给了该方法。验证是针对数据或传递的值进行的,这些值通常以字典的形式传递。验证结束时需要将数据对象传回,这表明你可以使用此方法来修改数据,就像 BeforeValidator 一样。
Alias
Alias非常重要,尤其是在处理传入数据并执行转换时。我们使用Alias来更改值的名称,或者在值未作为字段名传递时定位它们。
Pydantic 将 Alias 定义为验证 Alias(传入值的名称与字段不同)和序列化 Alias(验证后序列化或输出数据时更改名称)。
文档详细介绍了如何使用 Field() 对象定义 Alias,但这样做存在一些问题。同时定义默认值和 Alias 不起作用。不过,我们可以在 Model 级别而不是字段级别定义 Alias。
from pydantic import AliasGenerator, BaseModel, ConfigDict
class Tree(BaseModel):
model_config = ConfigDict(
alias_generator=AliasGenerator(
validation_alias=lambda field_name: field_name.upper(),
serialization_alias=lambda field_name: field_name.title(),
)
)
age: int
height: float
kind: str
t = Tree.model_validate({'AGE': 12, 'HEIGHT': 1.2, 'KIND': 'oak'})
print(t.model_dump(by_alias=True)){'Age': 12, 'Height': 1.2, 'Kind': 'oak'}我从文档中引用了这个例子,因为它有点简单,而且用处不大,但它确实展示了如何转换字段名称。这里需要注意的是,如果要使用序列化 Alias 序列化 Model,则需要设置by_alias=True。
下面我们一起学习一些使用 AliasChoices 和 AliasPath 对象的更有用的 Alias 示例。
AliasChoices
发送给你的数据中,同一个值会被赋予不同的字段名或列名,这种情况很常见。我敢打赌,如果你让十几个人给你发送一份包含姓和名的姓名列表,你肯定会得到不同的列名!
AliasChoices 定义与给定字段匹配的传入值名称列表。
from pydantic import BaseModel, ConfigDict, AliasGenerator, AliasChoices
aliases = {
"first_name": AliasChoices("fname", "surname", "forename", "first_name"),
"last_name": AliasChoices("lname", "family_name", "last_name")
}
class FirstNameChoices(BaseModel):
model_config = ConfigDict(
alias_generator=AliasGenerator(
validation_alias=lambda field_name: aliases.get(field_name, None)
)
)
title: str
first_name: str
last_name: str此处代码定义一个字典,其中键是字段名称,值是 AliasChoices 对象。请注意,我在列表中包含了实际的字段名称。你可能使用它来转换和序列化要保存的数据,然后希望将其读回Model以供使用。因此,实际的字段名称应该包含在列表中。
AliasPath
大多数情况下,传入的数据并非扁平的,而是 JSON 格式的 blob,这些 blob 会被转换成字典,然后传递给Model。那么,如何将字段设置为字典或列表中的值呢?AliasPath 就是用来做这件事的。
from pydantic import BaseModel, ConfigDict, AliasGenerator, AliasPath
aliases = {
"first_name": AliasPath("name", "first_name"),
"last_name": AliasPath("name", "last_name")
}
class FirstNameChoices(BaseModel):
model_config = ConfigDict(
alias_generator=AliasGenerator(
validation_alias=lambda field_name: aliases.get(field_name, None)
)
)
title: str
first_name: str
last_name: str
obj = FirstNameChoices(**{"name":{"first_name": "marc", "last_name": "Nealer"},"title":"Master Of All"})从上面的代码中可以看到,姓和名都存储在一个字典中。我使用了 AliasPath 来扁平化数据,将值从字典中提取出来,这样所有值都位于同一层级。
使用 AliasPath 和 AliasChoices
我们可以将这两者一起使用。
from pydantic import BaseModel, ConfigDict, AliasGenerator, AliasPath, AliasChoices
aliases = {
"first_name": AliasChoices("first_name", AliasPath("name", "first_name")),
"last_name": AliasChoices("last_name", AliasPath("name", "last_name"))
}
class FirstNameChoices(BaseModel):
model_config = ConfigDict(
alias_generator=AliasGenerator(
validation_alias=lambda field_name: aliases.get(field_name, None)
)
)
title: str
first_name: str
last_name: str
obj = FirstNameChoices(**{"name":{"first_name": "marc", "last_name": "Nealer"},"title":"Master Of All"})写在最后
Pydantic 是一个超级优秀的库,但它也存在一些问题,就是实现同一件事的方法太多了。为了理解和使用我这里展示的示例,我付出了很多努力。我希望通过这些示例,你能比我更快地上手 Pydantic,并且减少很多工作量。
最后一件事。Pydantic 和 AI 服务。Chat-gtp、Gemini 等对 Pydantic 的问题给出的答案总是很古怪。就好像它无法确定自己使用的是 Pydantic V1 还是 V2,所以总是搞混。你甚至会听到“Pydantic 做不到”之类的话来反驳它能做到的事情。所以在使用库的时候最好避免使用这些库。
相关推荐
- 电脑如何重装系统winxp(电脑如何重装xp系统教程)
-
重装XP系统的步骤如下:1.首先备份您的重要文件和数据。2.插入XP系统安装光盘,重新启动计算机。3.在启动时按下计算机制造商指定的键(通常是F2、F12、Esc等)进入BIOS设置或启动菜单。...
- win8密钥永久激活工具(win8密钥永久激活工具在哪)
-
win8.1正式版升级win10,自动激活,win8.1的密钥不能激活win10,升级win10正式版以后,无论怎么安装win10,系统都是自动激活状态。终极PID检测产品密钥:GCRJD...
- 制作系统安装u盘教程(制作系统安装盘及系统安装的步骤)
-
答u盘做系统详细步骤如下: 1.首先我们要准备一个10G内存的空白u盘,然后在装机吧官网下载一个装机吧一键重装系统软件,安装完成后打开软件,制作模式选择”USB-HDD“分区格式为”NTFS“点击...
-
- 联想win7怎么进入bios设置(联想win7进入bios设置win10)
-
联想电脑win7进入bios设置的具体步骤如下: 1、首先我们打开电脑的同时,按下键盘上的“F2”。2、然后我们在弹出来的窗口中就可以进入到BIOS界面中。3、然后我们按下键盘上的“F10”,之后回车确定即可退出。联想电脑win7...
-
2025-11-09 14:03 off999
- 优盘里面的文件被误删了能否找回
-
如果您的文件在很久以前被误删并且没有进行任何操作,那么有可能通过一些专业的数据恢复工具来恢复被删除的文件。以下是一些可能的操作步骤:1.停止使用U盘:为了最大限度地提高恢复成功的几率,请停止使用U盘...
- 电脑系统程序下载(电脑应用程序下载)
-
1、首先下载并安装DriverDroid,运行后根据设置向导进行设置。2、然后注意安卓手机已获取ROOT,设置时需要连接电脑。3、将手机自动下载的bios文件移动到镜像根目录下(手机内存/Downlo...
- 万能网卡驱动离线安装包下载
-
电脑没网是吧,那你可以先用手机下载。之后放电脑上安装的万能网卡驱动下载地址http://drivers.160.com/universal/camera.html该驱动能够使大部分的网卡能够被系统...
- 正版office和盗版区别(office正版和盗版可以共存吗)
-
区别主要有三方面:1.office正版是付费的,而且价格相对而言较高,盗版呢价格相对低或者干脆免费。2.office正版因为是官方发行,文件肯定完整,功能齐全,稳定。盗版呢一般都是破译的或者是拷贝的,...
- ヽ这个符号怎么打出来(这个符号怎么打出来是在中间的)
-
下载酷狗拼音,软键盘就有了。ˋ☆╲ヽ
- 120g固态硬盘够用吗(10几年的老电脑换个固态硬盘)
-
一般办公家用还是够用了,分两个区,系统盘分50G,剩余的分一个区做资料盘。特殊要求,资料文件比较多的话,128g是不够用,只能分一个区。这个主要取决于您电脑主要的用途,如果您的电脑只是用来日常办公和娱...
- 谷歌浏览器google(谷歌浏览器googleplay)
-
GoogleChrome,又称Google浏览器,是一个美国Google(谷歌)公司开发的网页浏览器。该浏览器是基于其他开源软件所撰写,包括WebKit,目标是提升稳定性、速度和安全性,并创造出简单且...
- android13正式版下载(安卓版本13)
-
出现该问题的原因是,用户在设置里开启了新下载的APP,仅添加到APP资源库选项。大家只要进入“设置-主屏幕”,把新下载的APP,改为“添加到主屏幕”即可解决问题。修改完成后,你再进入AppStore下...
- firefox浏览器安卓版(firefox浏览器安卓版 打开本地网页)
-
要进入火狐浏览器手机版的主页,你可以通过以下几种方式进行:首先,打开火狐浏览器App,然后点击右上角的三条横线菜单按钮,接着选择“主页”选项。另外,你也可以直接在浏览器地址栏中输入“about:hom...
- 电脑cpu性能排行榜天梯图(“电脑cpu性能天梯图”)
-
一、英特尔酷睿i7670。这款英特尔CPU采用的是超频新芯,最大程度的提升处理器的超频能力。二、英特尔酷睿i74790kCPU:这款CPU采用22纳米制程工艺的框架,它的默认频率是4.0到4.4Ghz...
欢迎 你 发表评论:
- 一周热门
-
-
抖音上好看的小姐姐,Python给你都下载了
-
全网最简单易懂!495页Python漫画教程,高清PDF版免费下载
-
Python 3.14 的 UUIDv6/v7/v8 上新,别再用 uuid4 () 啦!
-
python入门到脱坑 输入与输出—str()函数
-
宝塔面板如何添加免费waf防火墙?(宝塔面板开启https)
-
Python三目运算基础与进阶_python三目运算符判断三个变量
-
飞牛NAS部署TVGate Docker项目,实现内网一键转发、代理、jx
-
(新版)Python 分布式爬虫与 JS 逆向进阶实战吾爱分享
-
慕ke 前端工程师2024「完整」
-
失业程序员复习python笔记——条件与循环
-
- 最近发表
- 标签列表
-
- python计时 (73)
- python安装路径 (56)
- python类型转换 (93)
- python进度条 (67)
- python吧 (67)
- python的for循环 (65)
- python格式化字符串 (61)
- python静态方法 (57)
- python列表切片 (59)
- python面向对象编程 (60)
- python 代码加密 (65)
- python串口编程 (77)
- python封装 (57)
- python写入txt (66)
- python读取文件夹下所有文件 (59)
- python操作mysql数据库 (66)
- python获取列表的长度 (64)
- python接口 (63)
- python调用函数 (57)
- python多态 (60)
- python匿名函数 (59)
- python打印九九乘法表 (65)
- python赋值 (62)
- python异常 (69)
- python元祖 (57)
