跳转到内容

为何使用 Pydantic 验证?

如今,Pydantic 每月的下载量高达数百万次,并被世界上一些规模最大、最知名的组织所使用。

很难确切知道为何自六年前诞生以来,有如此多的人采用 Pydantic,但这里有几个猜测。

类型提示驱动模式验证

Pydantic 用于验证的模式通常由 Python 的类型提示定义。

类型提示非常适合此目的,因为如果你正在编写现代 Python 代码,你已经知道如何使用它们。使用类型提示也意味着 Pydantic 能与静态类型检查工具(如 mypyPyright)以及集成开发环境(IDE,如 PyCharmVSCode)很好地集成。

示例 - 仅使用类型提示
from typing import Annotated, Literal

from annotated_types import Gt

from pydantic import BaseModel


class Fruit(BaseModel):
    name: str  # (1)!
    color: Literal['red', 'green']  # (2)!
    weight: Annotated[float, Gt(0)]  # (3)!
    bazam: dict[str, list[tuple[int, bool, float]]]  # (4)!


print(
    Fruit(
        name='Apple',
        color='red',
        weight=4.2,
        bazam={'foobar': [(1, True, 0.1)]},
    )
)
#> name='Apple' color='red' weight=4.2 bazam={'foobar': [(1, True, 0.1)]}
  1. name 字段仅用 str 注解——任何字符串都是允许的。
  2. Literal 类型用于强制 color 字段的值必须是 'red''green'
  3. 即使我们想应用 Python 类型本身无法封装的约束,我们也可以使用 Annotatedannotated-types 来强制约束,同时仍然保持类型支持。
  4. 我并不是说“bazam”真的是水果的一个属性,而是为了展示可以轻松验证任意复杂的类型。

了解更多

请参阅关于支持的类型的文档

性能

Pydantic 的核心验证逻辑在一个独立的包(pydantic-core)中实现,其中大多数类型的验证逻辑是用 Rust 实现的。

因此,Pydantic 是 Python 中最快的数据验证库之一。

性能示例 - Pydantic vs. 专用代码

通常,专用代码应该比通用验证器快得多,但在这个例子中,当解析 JSON 和验证 URL 时,Pydantic 比专用代码快了超过 300%。

性能示例
import json
import timeit
from urllib.parse import urlparse

import requests

from pydantic import HttpUrl, TypeAdapter

reps = 7
number = 100
r = requests.get('https://api.github.com/emojis')
r.raise_for_status()
emojis_json = r.content


def emojis_pure_python(raw_data):
    data = json.loads(raw_data)
    output = {}
    for key, value in data.items():
        assert isinstance(key, str)
        url = urlparse(value)
        assert url.scheme in ('https', 'http')
        output[key] = url


emojis_pure_python_times = timeit.repeat(
    'emojis_pure_python(emojis_json)',
    globals={
        'emojis_pure_python': emojis_pure_python,
        'emojis_json': emojis_json,
    },
    repeat=reps,
    number=number,
)
print(f'pure python: {min(emojis_pure_python_times) / number * 1000:0.2f}ms')
#> pure python: 5.32ms

type_adapter = TypeAdapter(dict[str, HttpUrl])
emojis_pydantic_times = timeit.repeat(
    'type_adapter.validate_json(emojis_json)',
    globals={
        'type_adapter': type_adapter,
        'HttpUrl': HttpUrl,
        'emojis_json': emojis_json,
    },
    repeat=reps,
    number=number,
)
print(f'pydantic: {min(emojis_pydantic_times) / number * 1000:0.2f}ms')
#> pydantic: 1.54ms

print(
    f'Pydantic {min(emojis_pure_python_times) / min(emojis_pydantic_times):0.2f}x faster'
)
#> Pydantic 3.45x faster

与其他用编译语言编写的注重性能的库不同,Pydantic 还通过函数验证器为自定义验证提供了出色的支持。

了解更多

Samuel Colvin 在 PyCon 2023 上的演讲解释了 pydantic-core 的工作原理及其与 Pydantic 的集成方式。

序列化

Pydantic 提供了三种序列化模型的方式:

  1. 序列化为由相应 Python 对象组成的 Python dict
  2. 序列化为仅由“可 JSON 化”类型组成的 Python dict
  3. 序列化为 JSON 字符串。

在这三种模式下,都可以通过排除特定字段、排除未设置的字段、排除默认值以及排除 None 值来自定义输出。

示例 - 三种序列化方式
from datetime import datetime

from pydantic import BaseModel


class Meeting(BaseModel):
    when: datetime
    where: bytes
    why: str = 'No idea'


m = Meeting(when='2020-01-01T12:00', where='home')
print(m.model_dump(exclude_unset=True))
#> {'when': datetime.datetime(2020, 1, 1, 12, 0), 'where': b'home'}
print(m.model_dump(exclude={'where'}, mode='json'))
#> {'when': '2020-01-01T12:00:00', 'why': 'No idea'}
print(m.model_dump_json(exclude_defaults=True))
#> {"when":"2020-01-01T12:00:00","where":"home"}

了解更多

请参阅关于序列化的文档

JSON Schema

可以为任何 Pydantic 模式生成 JSON Schema——这使得 API 能够自我文档化,并能与支持 JSON Schema 格式的各种工具集成。

示例 - JSON Schema
from datetime import datetime

from pydantic import BaseModel


class Address(BaseModel):
    street: str
    city: str
    zipcode: str


class Meeting(BaseModel):
    when: datetime
    where: Address
    why: str = 'No idea'


print(Meeting.model_json_schema())
"""
{
    '$defs': {
        'Address': {
            'properties': {
                'street': {'title': 'Street', 'type': 'string'},
                'city': {'title': 'City', 'type': 'string'},
                'zipcode': {'title': 'Zipcode', 'type': 'string'},
            },
            'required': ['street', 'city', 'zipcode'],
            'title': 'Address',
            'type': 'object',
        }
    },
    'properties': {
        'when': {'format': 'date-time', 'title': 'When', 'type': 'string'},
        'where': {'$ref': '#/$defs/Address'},
        'why': {'default': 'No idea', 'title': 'Why', 'type': 'string'},
    },
    'required': ['when', 'where'],
    'title': 'Meeting',
    'type': 'object',
}
"""

Pydantic 兼容最新版本的 JSON Schema 规范(2020-12),该版本与 OpenAPI 3.1 兼容。

了解更多

请参阅关于 JSON Schema 的文档

严格模式和数据转换

默认情况下,Pydantic 对常见的错误类型持宽容态度,并将数据转换为正确的类型——例如,传递给 int 字段的数字字符串将被解析为 int

Pydantic 还有一个严格模式,在这种模式下,类型不会被转换,除非输入数据与预期的模式完全匹配,否则会引发验证错误。

但在验证 JSON 数据时,严格模式可能没什么用,因为 JSON 没有与许多常见 Python 类型(如 datetimeUUIDbytes)匹配的类型。

为了解决这个问题,Pydantic 可以在一个步骤中解析和验证 JSON。这允许进行合理的数据转换(例如,将字符串解析为 datetime 对象)。由于 JSON 解析是在 Rust 中实现的,所以它的性能也非常高。

示例 - 真正有用的严格模式
from datetime import datetime

from pydantic import BaseModel, ValidationError


class Meeting(BaseModel):
    when: datetime
    where: bytes


m = Meeting.model_validate({'when': '2020-01-01T12:00', 'where': 'home'})
print(m)
#> when=datetime.datetime(2020, 1, 1, 12, 0) where=b'home'
try:
    m = Meeting.model_validate(
        {'when': '2020-01-01T12:00', 'where': 'home'}, strict=True
    )
except ValidationError as e:
    print(e)
    """
    2 validation errors for Meeting
    when
      Input should be a valid datetime [type=datetime_type, input_value='2020-01-01T12:00', input_type=str]
    where
      Input should be a valid bytes [type=bytes_type, input_value='home', input_type=str]
    """

m_json = Meeting.model_validate_json(
    '{"when": "2020-01-01T12:00", "where": "home"}'
)
print(m_json)
#> when=datetime.datetime(2020, 1, 1, 12, 0) where=b'home'

了解更多

请参阅关于严格模式的文档

数据类、类型化字典等

Pydantic 提供了四种创建模式以及执行验证和序列化的方法:

  1. BaseModel — Pydantic 自己的超类,通过实例方法提供了许多常用工具。
  2. Pydantic 数据类 — 对标准数据类的封装,并执行额外的验证。
  3. TypeAdapter — 一种通用的方法,用于适配任何类型以进行验证和序列化。这使得像 TypedDictNamedTuple 这样的类型以及简单类型(如 inttimedelta)都能够被验证 — 所有支持的类型都可以与 TypeAdapter 一起使用。
  4. validate_call — 一个装饰器,用于在调用函数时执行验证。
示例 - 基于 TypedDict 的模式
from datetime import datetime

from typing_extensions import NotRequired, TypedDict

from pydantic import TypeAdapter


class Meeting(TypedDict):
    when: datetime
    where: bytes
    why: NotRequired[str]


meeting_adapter = TypeAdapter(Meeting)
m = meeting_adapter.validate_python(  # (1)!
    {'when': '2020-01-01T12:00', 'where': 'home'}
)
print(m)
#> {'when': datetime.datetime(2020, 1, 1, 12, 0), 'where': b'home'}
meeting_adapter.dump_python(m, exclude={'where'})  # (2)!

print(meeting_adapter.json_schema())  # (3)!
"""
{
    'properties': {
        'when': {'format': 'date-time', 'title': 'When', 'type': 'string'},
        'where': {'format': 'binary', 'title': 'Where', 'type': 'string'},
        'why': {'title': 'Why', 'type': 'string'},
    },
    'required': ['when', 'where'],
    'title': 'Meeting',
    'type': 'object',
}
"""
  1. 用于 TypedDictTypeAdapter 执行验证,它也可以直接使用 validate_json 验证 JSON 数据。
  2. 使用 dump_pythonTypedDict 序列化为 Python 对象,也可以使用 dump_json 序列化为 JSON。
  3. TypeAdapter 也可以生成 JSON Schema。

自定义

函数式验证器和序列化器,以及一个强大的自定义类型协议,意味着 Pydantic 的操作方式可以基于每个字段或每种类型进行自定义。

自定义示例 - 包装验证器

“包装验证器”是 Pydantic V2 中的新功能,是自定义验证最强大的方式之一。

from datetime import datetime, timezone
from typing import Any

from pydantic_core.core_schema import ValidatorFunctionWrapHandler

from pydantic import BaseModel, field_validator


class Meeting(BaseModel):
    when: datetime

    @field_validator('when', mode='wrap')
    def when_now(
        cls, input_value: Any, handler: ValidatorFunctionWrapHandler
    ) -> datetime:
        if input_value == 'now':
            return datetime.now()
        when = handler(input_value)
        # in this specific application we know tz naive datetimes are in UTC
        if when.tzinfo is None:
            when = when.replace(tzinfo=timezone.utc)
        return when


print(Meeting(when='2020-01-01T12:00+01:00'))
#> when=datetime.datetime(2020, 1, 1, 12, 0, tzinfo=TzInfo(3600))
print(Meeting(when='now'))
#> when=datetime.datetime(2032, 1, 2, 3, 4, 5, 6)
print(Meeting(when='2020-01-01T12:00'))
#> when=datetime.datetime(2020, 1, 1, 12, 0, tzinfo=datetime.timezone.utc)

了解更多

请参阅关于验证器自定义序列化器自定义类型的文档。

生态系统

在撰写本文时,GitHub 上有 466,400 个代码仓库和 PyPI 上有 8,119 个包依赖于 Pydantic。

一些依赖于 Pydantic 的知名库:

更多使用 Pydantic 的库可以在 Kludex/awesome-pydantic 找到。

使用 Pydantic 的组织

一些使用 Pydantic 的知名公司和组织,以及关于我们为何/如何知道他们在使用 Pydantic 的评论。

以下组织被列入是因为它们符合以下一个或多个标准:

  • 在其公共代码仓库中将 Pydantic 作为依赖项。
  • 从组织内部域名向 Pydantic 文档网站引荐流量——具体的引荐来源未包含在内,因为它们通常不属于公共领域。
  • Pydantic 团队与该组织雇佣的工程师就组织内部使用 Pydantic 的情况进行直接沟通。

在适当且信息已在公共领域的情况下,我们提供了一些额外的细节。

Adobe

adobe/dy-sql 使用 Pydantic。

亚马逊和 AWS

Anthropic

anthropics/anthropic-sdk-python 使用 Pydantic。

Apple (苹果)

(基于上述标准)

ASML (阿斯麦)

(基于上述标准)

AstraZeneca (阿斯利康)

AstraZeneca GitHub 组织中的多个仓库依赖于 Pydantic。

Cisco Systems (思科系统)

Comcast (康卡斯特)

(基于上述标准)

Datadog

  • DataDog/integrations-core 和其他仓库中广泛使用 Pydantic。
  • 与 Datadog 的工程师就他们如何使用 Pydantic 进行了沟通。

Facebook

facebookresearch GitHub 组织中的多个仓库依赖于 Pydantic。

GitHub

GitHub 在 2022 年赞助了 Pydantic 750 美元。

Google (谷歌)

google/turbinia 和其他仓库中广泛使用 Pydantic。

HSBC (汇丰银行)

(基于上述标准)

IBM

IBM GitHub 组织中的多个仓库依赖于 Pydantic。

Intel (英特尔)

(基于上述标准)

Intuit

(基于上述标准)

Intergovernmental Panel on Climate Change (政府间气候变化专门委员会)

解释 IPCC 如何使用 Pydantic 的推文

JPMorgan (摩根大通)

(基于上述标准)

Jupyter

  • Jupyter notebook 的开发者正在为子项目使用 Pydantic。
  • 通过基于 FastAPI 的 Jupyter 服务器 Jupyverse
  • FPS 的配置管理。

Microsoft (微软)

  • DeepSpeed 深度学习优化库广泛使用 Pydantic。
  • microsoft GitHub 组织中的多个仓库依赖于 Pydantic,特别是他们的
  • Pydantic 也在 Azure GitHub 组织中被使用
  • GitHub 上的评论显示,微软工程师在 Windows 和 Office 中使用 Pydantic。

Molecular Science Software Institute (分子科学软件研究所)

MolSSI GitHub 组织中的多个仓库依赖于 Pydantic。

NASA (美国国家航空航天局)

NASA GitHub 组织中的多个仓库依赖于 Pydantic。

NASA 还在其 JWST 项目中通过 FastAPI 使用 Pydantic 来处理来自詹姆斯·韦伯空间望远镜的图像,详见这条推文

Netflix (奈飞)

Netflix GitHub 组织中的多个仓库依赖于 Pydantic。

NSA (美国国家安全局)

nsacyber/WALKOFF 仓库依赖于 Pydantic。

NVIDIA (英伟达)

NVIDIA GitHub 组织中的多个仓库依赖于 Pydantic。

根据他们的文档,其“Omniverse 服务”依赖于 Pydantic。

OpenAI

根据 GitHub 上的这个讨论,OpenAI 在其 ChatCompletions API 中使用 Pydantic。

据传,OpenAI 在其内部服务中广泛使用 Pydantic。

Oracle (甲骨文)

(基于上述标准)

Palantir

(基于上述标准)

Qualcomm (高通)

(基于上述标准)

Red Hat (红帽)

(基于上述标准)

Revolut

据传,Revolut 的所有内部服务都使用 FastAPI 构建,因此也使用了 Pydantic。

Robusta

robusta-dev/robusta 仓库依赖于 Pydantic。

Salesforce

Salesforce 在 2022 年赞助了 Samuel Colvin 10,000 美元用于 Pydantic 的开发工作。

Starbucks (星巴克)

(基于上述标准)

Texas Instruments (德州仪器)

(基于上述标准)

Twilio

(基于上述标准)

Twitter

Twitter 的 the-algorithm 仓库,即他们开源的推荐引擎,使用了 Pydantic。

UK Home Office (英国内政部)

(基于上述标准)