/发布

Pydantic v2.7 的新功能和性能改进

Sydney Runkle avatar
Sydney Runkle
10 分钟

Pydantic v2.7 现已发布!此版本是我们自 v2.0 以来规模最大的一次发布,重点关注性能改进和高度需求的新功能。此版本还汇集了 30 多位新贡献者的工作成果!在本篇文章中,我们将介绍此版本的亮点。

您可以查看完整的更改日志 此处

Pydantic 的 JSON 解析器 提供了对部分 JSON 解析的支持。此功能允许解析器读取输入,直到遇到无效语法,并尽力返回一个准确表示输入有效部分的 JSON 对象。通过 from_json 方法公开,此功能对于处理来自大型语言模型 (LLM) 的流输出特别有价值,这些模型通常会生成传统解析器无法处理而不会出错的部分 JSON 对象。

此功能如此有用的原因之一是它有利于验证 LLM 输出。特别是,LLM 经常返回一个语法上不正确的 JSON 部分对象。过去,如果不发生 JSON 解析错误,则无法解析此类响应。现在,您可以启用部分 JSON 解析来解析响应,然后随后使用 model_validate 对解析的对象进行验证。

这是一个简单的示例

from pydantic_core import from_json

partial_json_data = '["aa", "bb", "c'  # (1)!

try:
    result = from_json(partial_json_data, allow_partial=False)
except ValueError as e:
    print(e)  # (2)!
    #> EOF while parsing a string at line 1 column 15

result = from_json(partial_json_data, allow_partial=True)
print(result)  # (3)!
#> ['aa', 'bb']
  1. JSON 列表不完整 - 缺少结束的 "]
  2. allow_partial 设置为 False(默认值)时,会发生解析错误。
  3. allow_partial 设置为 True 时,输入的一部分会成功反序列化。

您可以从我们的一些 博文 中了解有关将 Pydantic 集成到您的 LLM 工作中的更多信息。

有关更多信息,请查看此新功能的 文档

Pydantic 支持 SecretStrSecretBytes 类型,这些类型用于表示敏感数据。我们已将此支持扩展到包括一个泛型 Secret 基类型,该类型可用于创建自定义的秘密类型。

例如,您可以创建一个 SecretSalary 类型,它包装一个整数工资值并自定义秘密值的显示,如下所示

from datetime import date

from pydantic import BaseModel, Secret

class SecretSalary(Secret[int]):
    def _display(self) -> str:
        return '$******'


class Employee(BaseModel):
    name: str
    salary: SecretSalary


employee = Employee(name='John Doe', salary=100_000)

print(repr(employee))
#> Employee(name='John Doe', salary=SecretSalary('$******'))

print(employee.salary)
#> $******

print(employee.salary.get_secret_value())
#> 100000

如果您对更通用的 repr 输出感到满意,则可以使用这个更简洁的版本,其中 Secret 类型直接参数化,无需子类

from typing_extensions import Annotated

from pydantic import Secret, TypeAdapter

ta = TypeAdapter(Secret[int])

my_secret_int = ta.validate_python(123)
print(my_secret_int)
#> **********

print(my_secret_int.get_secret_value())
#> 123

此功能具有极强的扩展性,可用于为各种基本类型创建自定义的秘密类型。

探索 使用文档 以了解更多信息!

Pydantic 中最 受高度需求 的功能(从不曾改变)之一是能够将字段标记为已弃用。感谢 @Viicos 的辛勤工作,此功能已实现!

将字段标记为 deprecated 将导致

  1. 访问字段时发出运行时弃用警告
  2. 在生成的 JSON 模式中将 deprecated 参数设置为 true

deprecated 字段可以设置为以下任何一项:

  • 字符串,将用作弃用消息。
  • warnings.deprecated 装饰器(或 typing_extensions 反向移植)的实例。
  • 布尔值,将用于使用默认的“已弃用”弃用消息将字段标记为已弃用。

这是一个简单的示例

from pydantic import BaseModel, Field


class Model(BaseModel):
    deprecated_field: int = Field(deprecated=True)

print(Model.model_json_schema()['properties']['deprecated_field'])
#> {'deprecated': True, 'title': 'Deprecated Field', 'type': 'integer'}

此功能的 文档 深入探讨了有关标记和自定义已弃用字段的各种方法的更多详细信息。

在 v1 中,Pydantic 默认使用带有鸭子类型的序列化。为了提高安全性,Pydantic v2 停止使用这种方法。

在 Pydantic v2.7 中,我们已通过新的 serialize_as_any 运行时标志重新引入了带有鸭子类型的序列化作为一种可选功能。此可选功能在以前的 v2.X 版本中可以通过 SerializeAsAny 注解获得,但这需要单独注释每个字段。新的 serialize_as_any 标志允许您使用单个标志为模型中的所有字段启用鸭子类型序列化。

这是一个展示设置基本用法的示例

from pydantic import BaseModel, TypeAdapter


class User(BaseModel):
    name: str


class UserLogin(User):
    password: str


ta = TypeAdapter(User)
user_login = UserLogin(name='John Doe', password='some secret')

print(ta.dump_python(user_login, serialize_as_any=False))  # (1)!
#> {'name': 'John Doe'}

print(ta.dump_python(user_login, serialize_as_any=True))  # (2)!
#> {'name': 'John Doe', 'password': 'some secret'}
  1. 这是默认行为 - 模式中不存在的字段不会被序列化。
  2. serialize_as_any 设置为 True 时,模式中不存在的字段会被序列化。

我们已升级了 带有鸭子类型的序列化 的文档。此部分 特别介绍了新的 serialize_as_any 运行时标志。

Pydantic 以前在验证中支持 context,但在序列化中不支持。在 @ornariece 的帮助下,我们添加了在序列化期间使用 context 对象的支持。

这是一个简单的示例,我们使用 context 中提供的 unit 来转换 distance 字段

from pydantic import BaseModel, SerializationInfo, field_serializer

class Measurement(BaseModel):
    distance: float  # in meters

    @field_serializer('distance')
    def convert_units(self, v: float, info: SerializationInfo):
        context = info.context
        if context and 'unit' in context:
            if context['unit'] == 'km':
                v /= 1000 # convert to kilometers
            elif context['unit'] == 'cm':
                v *= 100  # convert to centimeters
        return v

measurement = Measurement(distance=500)

print(measurement.model_dump())  # no context
#> {'distance': 500.0}

print(measurement.model_dump(context={'unit': 'km'}))  # with context
#> {'distance': 0.5}

print(measurement.model_dump(context={'unit': 'cm'}))  # with context
#> {'distance': 50000.0}

此功能非常强大,因为它在序列化方面进一步扩展了 Pydantic 的灵活性和自定义功能。

请参阅 文档 以获取更多信息。

Pydantic 使用 PyO3 将我们的核心 Rust 代码连接到 Python。此幕后升级为 Pydantic 带来了显著的性能提升,如这些 基准测试 中所示。

有关 PyO3 0.21 中改进和更改的详细信息,请查看来自 Rust 🤝 Python 专家 David Hewitt 的这篇 博文

Pydantic 现在在 aarch64 (ARM) 平台上使用 SIMD 指令进行整数和字符串 JSON 解析。

enum 验证和序列化逻辑已移至用 Rust 编写的 pydantic-core。此迁移导致 enum 验证和序列化的速度提高了约 4 倍。

Pydantic 的 JSON 解析器 jiter 现在有一个 快速路径 用于创建 ASCII Python 字符串。此更改使 Python 字符串解析的性能提高了约 15%。

Pydantic 的 JSON 解析器 提供了对配置在 JSON 解析和验证期间如何缓存 Python 字符串的支持。缓存字符串时内存使用量会略有增加,但它可以显着提高性能,尤其是在某些字符串频繁重复的情况下。

cache_strings 设置(在 模型配置 中或作为 from_json 的参数)可以采用以下任何值:

  • True'all'(默认值):缓存所有字符串
  • 'keys':仅缓存字典键
  • False'none':不缓存

在此处了解有关此功能的更多信息 此处

凭借这些新功能和性能改进,Pydantic v2.7 成为迄今为止速度最快、功能最丰富的 Pydantic 版本。如果您有任何疑问或反馈,请打开Github 讨论。如果您遇到任何错误,请打开Github 问题