如何给 Pydantic 的列表字段设置默认值?

58

我希望创建一个 Pydantic 模型,其中有一个列表字段,如果未初始化,则默认值为一个空列表。是否有一种惯用的方法来实现这一点?

对于 Python 的内置数据类对象,您可以使用 field(default_factory=list),但是在我的实验中,这似乎会阻止我的 Pydantic 模型被 pickle 化。一个简单的实现可能是像这样的:

from pydantic import BaseModel

class Foo(BaseModel):
    defaulted_list_field: Sequence[str] = [] # Bad!

但我们都知道不要使用可变值,如空列表字面量作为默认值。

那么给Pydantic列表字段指定默认值的正确方式是什么?

2个回答

93

对于 pydantic,您可以使用可变默认值,例如:

class Foo(BaseModel):
    defaulted_list_field: List[str] = []

f1, f2 = Foo(), Foo()
f1.defaulted_list_field.append("hey!")

print(f1) # defaulted_list_field=['hey!']
print(f2) # defaulted_list_field=[]

它将被正确处理(深层复制),并且每个模型实例将拥有自己的空列表。


Pydantic 还具有 default_factory 参数。在空列表的情况下,结果将是相同的,它更适用于声明一个字段带有默认值时,你可能希望它是动态的(即对每个模型都不同)

from typing import List
from pydantic import BaseModel, Field
from uuid import UUID, uuid4

class Foo(BaseModel):
    defaulted_list_field: List[str] = Field(default_factory=list)
    uid: UUID = Field(default_factory=uuid4)


22
“它将被正确处理(深度复制),每个模型实例将拥有自己的空列表。” 这似乎在文档中没有明确说明(至少我没有找到),但这确实展示了pydantic非常精心设计的特点。 - kevlarr
1
不确定是因为什么改变了,还是因为我正在使用pydantic的@dataclass装饰器,但我得到了ValueError:mutable default <class 'list'> for field defaulted_list_field is not allowed: use default_factory - luckydonald
1
@dataclass 不允许像标准数据类那样有可变的默认字段。 - alex_noname
4
在 Pydantic 文档中有一个使用可变默认值的示例,链接在这里:https://pydantic-docs.helpmanual.io/usage/validators/#pre-and-per-item-validators。它使用了 List[int] = []。尽管它没有明确提到处理可变默认参数,但这个示例确实做到了。 - Tim Skov Jacobsen

13

在审查同事的合并请求时,我发现使用可变对象作为默认参数,并指出了这一点。让我惊讶的是,它的工作方式就好像已经对该对象进行了深层复制一样。我在项目自述文件中找到了一个示例,但没有任何解释。突然间我意识到开发人员长期以来一直忽略了这个问题(请参见底部的链接)。

实际上,你可以编写类似这样的内容, 并期望有正确的行为:

from pydantic import BaseModel

class Foo(BaseModel):
    defaulted_list_field: List[str] = []

但是,在底层会发生什么呢? 我们需要更深入一些...

在快速搜索源代码后,我找到了这个

class ModelField(Representation):
    ...
    def get_default(self) -> Any:
        return smart_deepcopy(self.default) if self.default_factory is None else self.default_factory()

在IT技术中,smart_deepcopy函数是:

def smart_deepcopy(obj: Obj) -> Obj:
    """
    Return type as is for immutable built-in types
    Use obj.copy() for built-in empty collections
    Use copy.deepcopy() for non-empty collections and unknown objects
    """

    obj_type = obj.__class__
    if obj_type in IMMUTABLE_NON_COLLECTIONS_TYPES:
        return obj  # fastest case: obj is immutable and not collection therefore will not be copied anyway
    try:
        if not obj and obj_type in BUILTIN_COLLECTIONS:
            # faster way for empty collections, no need to copy its members
            return obj if obj_type is tuple else obj.copy()  # type: ignore  # tuple doesn't have copy method
    except (TypeError, ValueError, RuntimeError):
        # do we really dare to catch ALL errors? Seems a bit risky
        pass

    return deepcopy(obj)  # slowest way when we actually might need a deepcopy

此外,正如评论中提到的那样,在数据库属性声明中不能直接使用可变默认值(请使用default_factory代替)。因此,这个例子 是无效的

from pydantic.dataclasses import dataclass

@dataclass
class Foo:
    bar: list = []

并给出:

ValueError: mutable default <class 'list'> for field bar is not allowed: use default_factory

开放讨论的链接 (目前没有答案):


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,