百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

强烈推荐一个 Python 神库——Pydantic

off999 2025-05-25 14:50 31 浏览 0 评论

初识 Pydantic

当我第一次接触 FastAPI 时,不可避免地遇到了 Pydantic。在 FastAPI 的生态中,Pydantic 几乎是不可或缺的——它负责请求/响应数据的校验、序列化和转换。然而,我的初次体验并不顺利:它的学习曲线略显陡峭,而且似乎提供了多种方式来完成同一件事,却没有明确的“最佳实践”指引。

尽管如此,随着深入使用,我逐渐意识到 Pydantic 的强大之处。它不仅仅是一个数据校验工具,更是一个能极大提升代码健壮性和可维护性的库。如今,它已成为我最喜爱的 十大 Python 库 之一。

版本说明与注意事项

在深入探讨之前,有两点需要特别注意:

  1. 本文基于 Pydantic v2:Pydantic v1 和 v2 之间存在显著差异,许多旧版代码在新版本中已不适用。
  2. 谨慎使用 AI 辅助工具(如 ChatGPT/Gemini):它们给出的示例可能是 v1 和 v2 的混合体,容易导致兼容性问题。

Pydantic 是什么?

Pydantic 是一个基于 Python 类型注解的 数据验证与解析库,其核心功能包括:

  • 数据校验(Validation):确保输入数据符合预期结构(如类型检查、必填字段、取值范围等)。
  • 数据转换(Parsing & Conversion):自动将原始数据(如 JSON、字典)转换为 Python 对象,并支持自定义转换逻辑。
  • 序列化(Serialization):将 Python 对象转换为 JSON、字典等格式,便于 API 传输或存储。

为什么选用 Pydantic?

  1. 与 Python 类型系统深度集成:利用 typing 模块(如 str, int, List, Optional)定义数据Model,减少样板代码。
  2. 运行时数据安全保障:自动校验数据,避免无效或恶意输入进入业务逻辑。
  3. 无缝集成 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 还提供了 AfterValidatorWrapValidator。前者在默认验证器之后运行,后者则像中间件一样,在验证器之前和之后执行操作。我们还可以应用多个验证器

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

下面我们一起学习一些使用 AliasChoicesAliasPath 对象的更有用的 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 做不到”之类的话来反驳它能做到的事情。所以在使用库的时候最好避免使用这些库。

相关推荐

电脑分辨率在哪里调(win10分辨率怎么调)

调整电脑分辨率的方法取决于您使用的是Windows、macOS还是Linux操作系统。以下是针对这三种操作系统的调整分辨率方法:1.Windows操作系统:-在桌面空白处右键单击,选择"显...

rar暴力破解器安卓版(暴力破解器压缩包)

安卓手机锁屏密码忘了,解决方法步骤如下:1.首先拆下手机电池,等待三秒钟以上时再装回电池,同时按下“音量上”和“电源键”并保持10秒钟以上时,手机自动进入recovery模式。2.在recovery模...

电脑开机慢是硬盘问题吗(电脑开机慢是硬盘坏了吗)

  电脑开机有两个含义,第一个就是通电,显示器上有显示;第二个是进系统。  电脑不装硬盘,只能达到第一种效果,系统肯定是进不去的,因为系统是装在硬盘上的,没有硬盘,就没有系统,也就启动不了。  当然,...

磁力种子(磁力种子搜索器怎么用)

BT的种子是指在BitTorrent文件分享协议中的一个文件或者目录,其中包含有一个或多个文件的元数据,例如文件名、大小、哈希值等信息。一个种子文件可以看做是一个索引,用来描述一个或多个文件的组成和布...

app store直接下载(app store直接下载软件)
  • app store直接下载(app store直接下载软件)
  • app store直接下载(app store直接下载软件)
  • app store直接下载(app store直接下载软件)
  • app store直接下载(app store直接下载软件)
windows图片查看器无法显示此图片

是因为没有正确配置Windows颜色系统默认设备文件造成的,解决该问题的具体步骤是:打开控制面板,查看方式选择“大图标”,单击打开“颜色管理”对话框,单击选中“高级”选项卡,将Windows颜色系统设...

ghost xp下载g(ghost Xp下载16)
  • ghost xp下载g(ghost Xp下载16)
  • ghost xp下载g(ghost Xp下载16)
  • ghost xp下载g(ghost Xp下载16)
  • ghost xp下载g(ghost Xp下载16)
深度技术的win7系统怎么样(深度技术win7系统怎么安装教程)

所谓的纯净的win7系统应该说的就是原版的win7系统,相对于Ghost版的系统来说,原版的win7系统是微软发布的未经过第三方修改过的纯净版系统,安装好后,它所有的功能和软件都是微软官方的,不会添加...

电脑怎么安全模式开机(电脑怎么安全模式开机启动)

电脑开机后进入安全模式的步骤如下:重启电脑:在开机时,狂按F8键,即可进入启动菜单选择界面。选择安全模式:在启动菜单选择界面中,可以看到三个版本的安全模式可以选择,方向键上下调整,然后按下回车键即可。...

win10企业版长期服务版(win10企业版 长期服务版)

Windows10企业版和企业长期服务版是微软为企业用户提供的两个版本,二者主要区别如下:1.版本周期不同。企业版(Enterprise)每年更新两次,每个版本的支持期限仅为18个月,而企业长期服...

mercury管理页面网址(mercury设置网址是什么)

要进入mercury路由器的管理页面,首先需要将电脑与路由器连接,确保网络连接正常。接着在浏览器中输入路由器的默认IP地址(通常为192.168.1.1),按下回车键。输入用户名和密码(默认用户名和密...

qq手机版官方(qq手机版官方免费下载安装)

z.qq.com可以通过以下方式登录手机QQ空间:1、使用手机登录手机腾讯网3g.qq.com,点击“空间”,根据提示QQ号码和QQ密码就可以登录;2、通过手机直接输入手机QQ空间网址z.qq.co...

打印机驱动安装不了10个方法
  • 打印机驱动安装不了10个方法
  • 打印机驱动安装不了10个方法
  • 打印机驱动安装不了10个方法
  • 打印机驱动安装不了10个方法
w7旗舰版系统怎么恢复出厂设置啊

方法一:1、左键单击任务栏开始按钮2、在启动项菜单右侧找到“控制面板”并左键单击3、在打开的界面中找到“区域和语言”选项并左键单击4、在弹出窗口中选择“键盘和语言”,在“选择显示语言”下...

ubuntu下载安装(Ubuntu下载安装包)

要在Ubuntu上从官方网站下载和安装Evolution,您可以按照以下步骤进行操作:1.打开您的网页浏览器,访问Ubuntu的官方网站:https://ubuntu.com。2.点击页面顶部的“...

取消回复欢迎 发表评论: