Pydantic 模型字段与 Typing.Optional[] 与 Typing.Optional[] = None

Hic*_*idi 10 python annotations python-typing pydantic

创建Pydantictyping.Optional[]模型时隐式将可选属性设置为 None与显式分配之间有什么区别?在这两种情况下,属性最终都会在类对象实例化时具有一个值。typing.Optional[] = NoneNone

import typing
import pydantic


class Bar(pydantic.BaseModel):
    a: typing.Optional[int]

    @pydantic.validator('a', always=True, pre=True)
    def check_a(cls, v, values, field):
        print("WITHOUT NONE")
        print(values)
        print(field)
        return v


class Baz(pydantic.BaseModel):
    a: typing.Optional[int] = None

    @pydantic.validator('a', always=True, pre=True)
    def check_a(cls, v, values, field):
        print("WITH NONE")
        print(values)
        print(field)
        return v


print(Bar())
print(Baz())
Run Code Online (Sandbox Code Playgroud)

输出:

WITHOUT NONE
{}
name='a' type=Optional[int] required=False default=None
a=None
WITH NONE
{}
name='a' type=Optional[int] required=False default=None
a=None
Run Code Online (Sandbox Code Playgroud)

Dan*_*erg 10

注意,这仅适用于 v1。对于 v2,请检查 @elachere 的答案。

长话短说

没有区别。至少在功能方面是这样。隐式地将默认值设置为None(除非指定了另一个默认值)。另一个显式地将默认值设置为None


细节

故意(?)不一致

您可能已经知道它Optional[T]相当于Union[T, None]or T | None(在新的表示法中)。在所有其他情况下,使用类型TU简单地将字段注释为a: T | U而不提供显式默认值将使其成为必填字段。在 Pydantic 术语中,这意味着如果不(以某种方式)为该字段提供值,则无法实例化模型,并且该值必须是类型T或类型U才能通过验证。

并集T | None是该规则的一个例外,它(可以说)并不明显,因此您可以将其称为不一致。但这似乎是故意的。使用这样的联合注释字段(例如a: Optional[T])将始终创建一个不需要的字段并且具有默认值None(当然除非您指定其他默认值)。

实施细节

在幕后,这种情况发生在模型创建期间,在ModelField实例几乎创建完毕并prepare调用其方法之后。它调用该_type_analysis方法来确定有关字段类型的更多详细信息。在识别出类型 origin 是union后,依次查看其类型参数,一旦确定其中之一是 theNoneType,则将该字段的required属性设置为False,并将其allow_none属性设置为True。然后,稍后,因为该字段仍然有一个未定义的default属性,所以该属性被设置为None如果您显式定义默认值(None或某些实例T)或默认工厂,则显然会跳过最后一步。

运行时影响

实际上,这意味着对于以下模型,所有字段的行为都相同:

  1. 初始化期间都不需要,因为
  2. None当没有提供其他值时,所有这些都将收到其值,
  3. 并且它们每个的类型设置为Optional[str]
from typing import Optional, Union
from pydantic import BaseModel, Field


class Model(BaseModel):
    a: str | None
    b: Union[str, None]
    c: Optional[str]
    d: Optional[str] = None
    e: Optional[str] = Field(default=None)
    f: Optional[str] = Field(default_factory=lambda: None)
    g: str = None


obj = Model()
print(obj.json(indent=4))
Run Code Online (Sandbox Code Playgroud)

输出:

{
    "a": null,
    "b": null,
    "c": null,
    "d": null,
    "e": null,
    "f": null,
    "g": null
}
Run Code Online (Sandbox Code Playgroud)

请注意,即使我们用 just 注释它, Even 也是以g将其类型设置为 be 的方式隐式处理的。您可以通过执行以下操作来验证这一点:Optional[str]str

for field in Model.__fields__.values():
    print(repr(field))
Run Code Online (Sandbox Code Playgroud)

输出:

ModelField(name='a', type=Optional[str], required=False, default=None)
ModelField(name='b', type=Optional[str], required=False, default=None)
ModelField(name='c', type=Optional[str], required=False, default=None)
ModelField(name='d', type=Optional[str], required=False, default=None)
ModelField(name='e', type=Optional[str], required=False, default=None)
ModelField(name='f', type=Optional[str], required=False, default_factory='<function <lambda>>')
ModelField(name='g', type=Optional[str], required=False, default=None)
Run Code Online (Sandbox Code Playgroud)

顺便说一下,你还可以创建一个typeOptional[T] required的字段。为此,您只需将默认值设置为省略号...,例如,field: Optional[str] = ...可以使该字段接受None作为值,但仍然要求您在初始化期间提供一个值。(参见文档

整洁代码注意事项

当然,这是比较主观的,但我建议遵循Python 的禅宗

显式的比隐式的好。

特别是因为这种行为非常不一致且不明显,除非您已经熟悉 Pydantic,所以即使可以,也不应该省略默认值。只需要您再添加几个字符 = None到字段定义中,但对于您或其他阅读代码的人来说,它会变得更加清晰。

我也不建议从注释中省略NoneType原因是一样的。上一个示例中的字段g可能工作得很好,但它的类型发生了隐式更改。

我的建议是field: Optional[str] = None,如果您只处理一种附加类型field: str | int | None,则执行 ;如果有更多类型,则执行 (或旧的Union表示法,如果您使用 Python <=3.9)。


Lon*_*oub 7

虽然之前的答案对于 pydantic v1 是正确的,但请注意, 2023 年 6 月 30 日发布的pydantic v2更改了此行为。

如迁移指南中所述:

Pydantic V2 更改了一些用于指定是否需要注释为Optional的字段(即没有默认值)或不需要(即具有 None 或相应类型的任何其他值的默认值)的逻辑,现在更接近匹配数据类的行为。同样,注释为 Any 的字段不再具有默认值 None。