Pydantic根验证器无法访问类属性。

huangapple go评论75阅读模式
英文:

Pydantic root validator unable to access class attributes

问题

这是一个最小的示例

```python
class BaseClass(BaseModel):
    description: str

    @root_validator(pre=True)
    def initialize(cls, values: Dict[str, Any]) -> Dict[str, Any]:
        print(cls.description) 
        # 也尝试过 print(values.get("description"), 它是None
        return values


class SubClass(BaseClass):
    description = "foo"

抛出错误
type object 'SubClass' has no attribute 'description'", "errorType": "AttributeError

我需要根据子类提供的字段执行一些验证。这个验证将在所有子类中通用。

英文:

Here's a minimal example:

class BaseClass(BaseModel):
    description: str

    @root_validator(pre=True)
    def initialize(cls, values: Dict[str, Any]) -> Dict[str, Any]:
        print(cls.description) 
        # also tried print(values.get("description"), which is None
        return values


class SubClass(BaseClass):
    description = "foo"

Throws error

type object 'SubClass' has no attribute 'description'", "errorType": "AttributeError

I need to do some validation based on the fields provided by the child class. This validation will be common across all the child classes.

答案1

得分: 1

解决方案是对 description 使用 ClassVar 注释。

对于 Pydantic 模型,只需在类命名空间中添加 name: typename: type = value 将在该模型上创建一个 字段,而不是一个类属性。如果你感兴趣,我在 这篇帖子 中更详细地解释了 Pydantic 字段与常规属性的区别(那里的主题是私有属性,但解释仍然相关)。

你希望 description类属性,即不是模型的字段,可以在不同实例上分配不同值。Pydantic 文档中的 自动排除属性 部分说:(重点是我的)

以下划线开头的类变量和用 typing.ClassVar 注释的属性将自动排除在模型之外。

所以为了实现你的目标,你需要明确告诉模型 description 应该是一个 ClassVar,而不是一个普通字段:

from typing import Any, ClassVar

from pydantic import BaseModel, root_validator


class BaseClass(BaseModel):
    description: ClassVar[str]  # 不是字段
    spam: str
    eggs: int

    @root_validator(pre=True)
    def initialize(cls, values: dict[str, Any]) -> dict[str, Any]:
        if cls.description == "foo":
            values["spam"] = values["spam"].upper()
            values["eggs"] = values["eggs"] ** 2
        return values


class SubClass(BaseClass):
    description = "foo"


instance = SubClass(spam="abc", eggs=2)
print(instance.json(indent=4))

输出:

{
    "spam": "ABC",
    "eggs": 4
}
英文:

The solution is to use a ClassVar annotation for description.

With Pydantic models, simply adding a name: type or name: type = value in the class namespace will create a field on that model, not a class attribute. If you are interested, I explained in a bit more detail how Pydantic fields are different from regular attributes in this post. (The topic there is private attributes, but the explanation is still relevant.)

You want description to be class attribute, i.e. not a field of the model that can be assigned different values on different instances. The section on automatically excluded attributes of the Pydantic docs say: (emphasis mine)

> Class variables which begin with an underscore and attributes annotated with typing.ClassVar will be automatically excluded from the model.

So to achieve what you want, you need to explicitly tell the model that description is supposed to be a ClassVar, not a normal field:

from typing import Any, ClassVar

from pydantic import BaseModel, root_validator


class BaseClass(BaseModel):
    description: ClassVar[str]  # not a field
    spam: str
    eggs: int

    @root_validator(pre=True)
    def initialize(cls, values: dict[str, Any]) -> dict[str, Any]:
        if cls.description == "foo":
            values["spam"] = values["spam"].upper()
            values["eggs"] = values["eggs"] ** 2
        return values


class SubClass(BaseClass):
    description = "foo"


instance = SubClass(spam="abc", eggs=2)
print(instance.json(indent=4))

Output:

{
    "spam": "ABC",
    "eggs": 4
}

答案2

得分: 0

root_validator内部,cls引用的是类对象本身,因此无法直接从cls中访问它,因为descriptionSubClass实例的属性。

您可以通过值字典在初始化根验证器中访问类属性。

此外,您需要移除pre=True,因为它会在模型初始化之前处理验证器。

from pydantic import BaseModel, root_validator
from typing import Dict, Any

class BaseClass(BaseModel):
    description: str

    @root_validator
    def initialize(cls, values: Dict[str, Any]) -> Dict[str, Any]:
        print(values.get("description"))
        return values

class SubClass(BaseClass):
    description = "foo"

SubClass()
英文:

There is an error because cls inside the root_validator is referring to the class object itself, you cannot access it directly from cls because description is an attribute of instances of SubClass.

You can access the class attributes in the initialize root validator via the values dictionary

Also you gonna need to remove pre=True because it processes the validator before the model initialization.

from pydantic import BaseModel, root_validator
from typing import Dict, Any

class BaseClass(BaseModel):
    description: str

    @root_validator
    def initialize(cls, values: Dict[str, Any]) -> Dict[str, Any]:
        print(values.get("description")) 
        return values

class SubClass(BaseClass):
    description = "foo"

SubClass()

答案3

得分: 0

要从子类访问描述属性,您可以使用__class__属性,该属性引用类对象本身。 "initialize"方法是一个类级别的方法,意味着它无法访问实例或类的属性。您可以尝试使用:

print(cls.__dict__.get("description"))
英文:

To access the description attribute from the child classes, you can use the class attribute, which refers to the class object itself. The "initialize" method is a class-level method meaning that is cannot access attributes of the instance or the class. You can try using:

print(cls.__dict__.get("description"))

huangapple
  • 本文由 发表于 2023年5月30日 08:08:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/76360917.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定