介绍

我最近没有太多时间听播客,但是当我有段时间第一次听 Talk Python to Me 时,它​​正在谈论 Pydantic(剧集链接是这里)。作者 Samuel Colvin 开始谈论计划于今年秋季进行的主要版本升级。使用 Rust 实现核心,速度提高 17 倍” 并被链接文档我读本文将介绍 Pydantic v2,重点介绍那里讨论的内容。

什么是Pydantic

在谈论 v2 之前,让我先简单谈谈 Pydantic 是什么。 Pydantic 是一个使用 Python 类型提示信息进行数据验证的库。预先定义数据的结构并检查输入数据是否与该结构匹配。

例如,考虑一个具有两个字段的数据结构,id(整数)和 name(字符串)。在 Pydantic 模型中定义它如下所示:

from pydantic import BaseModel

class User(BaseModel):
    id: int
    name: str = "John Doe"

只需扩展一个名为 BaseModel 的类并在类中对其进行类型提示。 name 的默认值为“John Doe”。我会试试这个。

>>> userdata = {
...     "id": 123,
...     "name": "Taro Yamada",
... }
>>> user = User(**userdata)
>>> user
User(id=123, name='Taro Yamada')
>>> user.id
123
>>> user.name
'Taro Yamada'

它可以毫无问题地作为用户类数据导入。另一方面,id 使用缺失数据。

>>> userdata = {
...     "name": "Taro Yamada",
... }
>>> user = User(**userdata)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "pydantic/main.py", line 341, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for User
id
  field required (type=value_error.missing)

我收到一条错误消息,提示“需要一个名为 id 的字段”。顺便说一句,使用缺少name 的数据会成功,因为将使用默认值。换句话说,似乎没有默认值的字段被视为必填字段。

这一次,虽然有id,但尝试使用字符串数据而不是整数。

>>> userdata = {
...     "id": "abc",
...     "name": "Taro Yamada",
... }
>>> user = User(**userdata)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "pydantic/main.py", line 341, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for User
id
  value is not a valid integer (type=type_error.integer)

我收到一条错误消息,提示“id 必须是整数”。

如果使用"id": "123",即使是字符串也会通过验证。它在从字符串任意转换为整数后进行检查(好心),但如果你只想严格接受整数,pydantic.StrictInt 而不是int 你可以使用

新版本V2

V2之路

最后,这里的主要主题是时间表。它以路线图的形式描述了在发布 V2 之前要采取的步骤。请注意,我正在查看的文档已于 2022 年 7 月 10 日更新,因此自编写以来,某些部分已经取得了进展。

  1. 发布具有更多功能的 pydantic-core 的第一个版本(见下文)
  2. V1.10 的合并工作
  3. 发布 V1.10
  4. 很抱歉没有进入 V1.10 的过时 PR
  5. master 重命名为 main,因为这是一个好机会
  6. main 用于 V2
  7. 开始为 V2 修改 Pydantic 并查看现有测试如何通过
  8. 使测试通过,再次更改,重复
  9. 发布 V2!

    我们将在 10 月底之前完成这项工作,或者最迟在今年年底之前完成!我是说。严重地?这是一个挑战者。

    Pydantic核心

    由于许多开发人员的贡献,Pydantic 得到了广泛的应用,但核心逻辑自第一次发布以来基本保持不变。所以我认为V2会是一个返工的好机会,并决定将核心部分删掉为pydantic-core。 Pydantic-core 在过去几个月一直在工作,是用 Rust 编写的。可以使用名为 PyO3 的库从 Python 调用它。使用 Rust 的三个原因。

    1. 性能
    2. 代码可见性和可扩展性
      • 函数调用速度很快,因此核心逻辑可以实现为相互调用的小型验证器。
    3. 安全
      • pydantic-core 需要区分复杂代码中的多种错误。 Rust 擅长这种事情,所以可以用更少的 bug 来实现(我认为)

      V2 的重大变化

      表现

      这里有基准测试结果,但通过在 Rust 中重新实现,V2 比 V1 快 4 到 50 倍,通常快 17 倍左右。

      严格模式

      在上面的示例中,我解释了字符串"123" 会自动转换为整数123 并通过验证。为了避免这种情况,我们必须指定一个特殊类型,但在 V2 中,添加了“严格模式”,允许您为每个模型和字段指定它。

      澄清数据转换规则

      非严格模式时会自动转换数据,但行为也很明确。例如,缺少数据将导致错误。例如,当尝试将123.1 作为int2020-01-01T12:00:00 作为date 接收时。

      包括 JSON 支持

      V2 解析 JSON 并将其转换为模型和目标类型。 (到目前为止,我想知道我是否曾经解析过 JSON 以创建 dict 类型的数据,然后从那里将其转换为模型或目标类型)

      无模型验证

      在 V1 中,模型对于验证是必需的,因此在模型创建和性能方面存在损失。使用 V2,您将能够直接验证单个字符串、日期时间类型、TypedDict 类型、数据类类型、URL 等。

      “必需”和“可空”清理

      在 V1 中,很容易混淆一个字段值是必需的并且它可以为 null 的事实。我们将在 V2 中阐明这一点。可空性通过在类型定义中添加| None 来指示,而必需性通过设置默认值来指示。

      from pydantic import BaseModel
      
      class Foo(BaseModel):
          f1: str  # 必須でNullにすることはできない
          f2: str | None  # 必須だけどNullを指定することは可能
          f3: str | None = None  # 必須ではなく、Nullを指定することも可能
          f4: str = 'Foobar'  # 必須ではないが、Nullには出来ない
      

      改进的验证器功能

      在 V1 中,验证器函数按顺序应用,但在 V2 中,它们可以在嵌套结构中调用。到目前为止,我们使用pre=True 来控制顺序,但从 V2 开始,根据条件,可以跳过不必要的验证器调用,因此可以更有效地执行验证。它还允许嵌套验证器捕获验证错误并返回默认值。

      更强大的别名

      (有这种例子,但是我还没有搞懂,因为我还没有执行环境。大概是“把baz的index=2的元素的qux的值改成这个值的bar 我想可以说

      from pydantic import BaseModel, Field
      
      
      class Foo(BaseModel):
          bar: str = Field(aliases=[['baz', 2, 'qux']])
      
      
      data = {
          'baz': [
              {'qux': 'a'},
              {'qux': 'b'},
              {'qux': 'c'},
              {'qux': 'd'},
          ]
      }
      
      foo = Foo(**data)
      assert foo.bar == 'c'
      

      改进的转储、序列化和导出

      从 V2 开始,model.dict() 将能够仅使用符合 JSON 的类型进行导出。比如在V1中,datetime类型是按原样写的,json.dumps()会报错(如果你直接model.json()就可以了)。

      验证上下文

      model_validatemodel_validate_json 现在可以将上下文信息作为参数传递。上下文是字典类型的数据,信息可以用任意键传递。

      在下面的示例中,支持的国家/地区代码信息是从数据库中检索并传递的。我们在验证器端使用它来检查home_country字段的国家代码是否包含在其中。

      from pydantic import BaseModel, EmailStr, validator
      
      class User(BaseModel):
          email: EmailStr
          home_country: str
      
          @validator('home_country')
          def check_home_country(cls, v, context):
              if v not in context['countries']:
                  raise ValueError('invalid country choice')
              return v
      
      async def add_user(post_data: bytes):
          countries = set(await db_connection.fetch_all('select code from country'))
          user = User.model_validate_json(post_data, context={'countries': countries})
          ...
      

      模型命名空间清理

      使用 Pydantic 创建的模型继承 pydantic.BaseModel。因此,无法创建与此处定义的方法名称具有相同字段名称的模型。在 V2 中,这被统一为以model_ 开头的方法名称,以便于理解。具体来说,它看起来像这样:

      • .model_validate()(前.parse_obj()
      • .model_validate_json()(原.parse_raw(..., content_type='application/json')
      • .model_is_instance()(新)
      • .model_is_instance_json()(新)
      • .model_dump()(原.dict()
      • .model_dump_json()(以前的.json()
      • .model_json_schema()(前.schema()
      • .model_update_forward_refs()(原.update_forward_refs()
      • .model_construct()(以前的 .construct()
      • .model_customize_schema()(新)
      • ModelConfig(原Config

      并且以下内容将被删除

      • .parse_file() - 我不小心把它放在 Pydantic
      • .parse_raw() - 要阅读 Json,请转到 .model_validate_json()。否则是错误的
      • .from_orm() - 去配置
      • .schema_json() - 替换为 json.dumps(m.model_json_schema())
      • .copy() - __copy__ 将被实现,copy 模块将被使用。

      严格的 API 和 API 文档

      V2 明确地将公共 API 与内部函数和类分开。里面的对象被放在_internal 子包中,以减少它们的可访问性。为公共 API 创建文档。mkdocstrings我打算用

      错误描述

      V2 将有更复杂的错误显示。它还包含详细说明该类型错误的网页的 URL,从而更容易理解错误是什么。

      纯 Python

      由于 pydantic-core 是用 Rust 编写的,因此它仅适用于可以安装二进制包的某些环境。支持的环境如下。

      • Linuxx86_64aarch64i686armv7lmusl-x86_64 musl-aarch64
      • 苹果系统x86_64arm64(python 3.7 除外)
      • 视窗amd64win32
      • 网络组装:wasm32(wasm上的python好像有问题,但是pydantic-core用wasm32编译通过单元测试)

      另一方面,Pydantic 本身是纯 Python。在 V1 中,使用 cython 用于编译使用环境以提高性能,但在 V2 中,此类部分提交给 pydantic-core。这使得 pydantic 更小的代码大小和更快的单元测试。

      is_instance 检查

      .model_is_instance() 方法可以通过 True/False 知道是否可以在不使用异常处理的情况下将数据转换为目标模型。

      停止使用解析

      在 V1 中,“parse”和“validate”这两个词混合在方法名称等中。 Pydantic 不仅仅是一个验证库,而且大多数人都使用“验证”,所以我决定在那里对其进行标准化。

      更改自定义字段类型

      由于我更改了调用验证器的方式,因此我无法再使用 V1 方法使用 __get_validators__ 定义自定义字段类型。相反,我让它查看__pydantic_validation_schema__ 属性,如果你在那里编写一个 pydantic-core 兼容模式,它将被应用。

      其他变化

      1. 您可以使用循环引用定义递归模型。我可以用 V1 做到这一点,但有时我陷入了 Python 的递归调用限制 (1000) 并出现错误。在 V2 中,它是用 Rust 实现的,可以正确处理。
      2. 我特别关注使用 wasm 编译和运行 pydantic-core,以便我可以在浏览器上编辑和运行 Pydantic V2 代码示例。
      3. 现在支持TypedDict,包括total=False
      4. from_orm变成from_attributes,会在schema生成时定义(在模型的Config或者字段的Config中)
      5. input_value 被添加到 ValidationError 中,以便更容易理解错误发生的原因。您现在可以在
      6. 架构中定义 on_error 逻辑,并选择是设置默认值还是在出错时将其留空。
      7. 改进了datetimedatetimetimedelta 验证。为此,我创建了一个名为 speedate 的新 Rust 库。
      8. 添加了“优先级”机制来合并或覆盖嵌套架构设置。
      9. 注释类型,因此您可以验证诸如 Annotated[set[int], Len(0, 10)] (一组具有 0 到 9 个元素的整数)或 Annotated[str, Len(1, 1024)] (具有 1 到 1023 个字符的字符串)之类的类型
      10. 一种validate装饰器可以在很多地方使用。
        • 函数(替换validate_arguments
        • 数据类。 pydantic.dataclasses.dataclass 是这个的别名
        • TypedDict
        • 所有支持的类型。例如,Union[...]Dict[str, Thing] 等。
        • 自定义字段类型。任何带有__pydantic_schema__ 属性的东西
      11. 提供了一种创建验证错误的简单方法。我特别想要一种在模型之外做的方法(还没有决定?)
      12. 模型的 __eq__ 的性能改进
      13. 计算字段。想法总是存在的,应该正确地做出
      14. 为子类的实例提供一种在不泄漏数据的情况下验证模型的方法(不确定??)
      15. 版本遵循 semvar 规则
      16. 在泛型类型中,M(GenericModel, Generic[T]) 必须写成。

        删除的功能和限制

        1. __root__ 不再需要自定义根模型,因为它允许在没有模型的情况下进行验证。
        2. .parse_file() 被删除。 .parse_raw()(json 验证)的一些功能将被.model_validate_json() 接管,其他的将被移除。
        3. .schema_json().copy()
        4. TypeError 不再被视为验证错误。在内部用于捕获函数验证器中的参数名称错误。
        5. 没有更多内置类型的子类strbytesint。要在用 Rust 编写的 pydantic-core 中进行这些验证,但如果需要,您可以使用包装验证器或自定义类型验证器
        6. 整数将在 Rust 的 i64 中表示。如果你想处理超过这个值的数字,你可以通过编写一个包装验证器来实现。
        7. 设置管理.我不打算删除该功能,但我认为可以将其与 Pydantic 分开,或者使其成为 Pydantic 的一个额外的包,以便可以使用pip install pydantic[settings] 安装它(似乎还没有决定然而)
        8. 以下配置属性将被删除
          • fields - 在制作 Field 之前就已经存在并且可以删除。
          • allow_mutation - 替换为 frozen
          • error_msg_templates - 没有很好的文档记录,错误消息可以用外部逻辑自定义
          • getter_dict - pydantic-core 已硬编码 from_attributes 逻辑
          • json_loads - 也在 pydantic-core 中硬编码
          • json_dumps - 可能删除
          • json_encoders - 参见上面对模式的讨论
          • underscore_attrs_are_private - 只需添加 PrivateAttr 与默认值
          • smart_union - 所有工会现在都“聪明”了

          概括

          我试图总结 Pydantic V2。由于不兼容的更改,看起来我们必须在一定程度上重写现有代码,但是随着速度的大幅提升,看起来我们将能够积极地做到这一点。而且我希望可以整体写清楚,因为它已经以各种方式进行了清理,而不仅仅是速度。

          到目前为止,已经提出了 SWIG、Pyrex、Cython 等作为提高 Python 性能的手段,但是 Pydantic V2 采用了一种新的手段来使用 Rust 提高速度,这使得我对 PyO3 库也有点兴趣,所以我也想找时间看看。


原创声明:本文系作者授权爱码网发表,未经许可,不得转载;

原文地址:https://www.likecs.com/show-308624048.html

相关文章:

  • 2021-11-08
  • 2022-12-23
  • 2021-04-26
  • 2021-11-20
  • 2021-08-30
  • 2021-05-04
  • 2021-06-04
猜你喜欢
  • 2021-04-18
  • 2021-04-06
  • 2022-12-23
  • 2021-07-12
  • 2021-10-06
  • 2022-12-23
  • 2022-12-23
相关资源
相似解决方案