如何比较字段值与前一个pydantic验证器?



我试过了:

from pydantic import BaseModel, validator

class Foo(BaseModel):
a: int
b: int
c: int
class Config:
validate_assignment = True
@validator("b", always=True)
def validate_b(cls, v, values, field):
# field - doesn't have current value
# values - has values of other fields, but not for 'b'
if values.get("b") == 0:  # imaginary logic with prev value
return values.get("b") - 1
return v

f = Foo(a=1, b=0, c=2)
f.b = 3
assert f.b == -1  # fails

还查找了属性设置,但显然它们不能与pydantic一起使用。

看起来像bug,所以我在github上做了一个问题:https://github.com/pydantic/pydantic/issues/4888

验证的工作方式是无状态的。当您创建一个模型实例时,在实例完全初始化之前就会运行验证。

您提到了文档中关于values参数的相关句子:

values:包含任何先前验证过的字段的名称到值映射的字典

如果我们暂时忽略赋值,对于您的示例验证器,这意味着在b之前已经验证的字段的值将存在于该字典中,这只是a的值。(因为验证器是按照字段定义的顺序运行的。)这个描述显然是针对在初始化期间运行的验证器,而不是赋值。

我承认的是,当验证值赋值时,文档留下了太多的空间来解释应该发生什么. 如果我们看一下BaseModel.__setattr__的源代码,我们可以非常清楚地看到意图:

def __setattr__(self, name, value):
...
known_field = self.__fields__.get(name, None)
if known_field:
# We want to
# - make sure validators are called without the current value for this field inside `values`
# - keep other values (e.g. submodels) untouched (using `BaseModel.dict()` will change them into dicts)
# - keep the order of the fields
if not known_field.field_info.allow_mutation:
raise TypeError(f'"{known_field.name}" has allow_mutation set to False and cannot be assigned')
dict_without_original_value = {k: v for k, v in self.__dict__.items() if k != name}
value, error_ = known_field.validate(value, dict_without_original_value, loc=name, cls=self.__class__)
...

正如你所看到的,它在注释中明确地声明values应该不包含当前值。

我们可以观察到这实际上是这里显示的行为:

from pydantic import BaseModel, validator

class Foo(BaseModel):
a: int
b: int
c: int
class Config:
validate_assignment = True
@validator("b")
def validate_b(cls, v: object, values: dict[str, object]) -> object:
print(f"{v=}, {values=}")
return v

if __name__ == "__main__":
print("initializing...")
f = Foo(a=1, b=0, c=2)
print("assigning...")
f.b = 3

输出:

<>之前初始化……V =0, values={'a': 1}分配……v = 3,值={"a":1、"c":2}

因此,这里没有错误。这是预期的行为。

这种行为是否合理或合理可能有争议。如果你想讨论这个问题,你可以把一个问题作为问题并询问为什么它是这样设计的,并提出一个合理的替代方法。

虽然在我个人看来,在当前的实现中更奇怪的是values在赋值期间包含任何。我认为这很奇怪,因为只有一个特定的值是被验证的。我理解values背后的意图的方式,它应该只在初始化期间可用。但那又是另一个争论了。

毫无疑问,验证器方法在赋值时的这种行为应该被明确地记录下来。这一点在前面提到的问题中也可能值得一提。