验证在 Python 中有许多属性的 class 的属性的最佳方法是什么?

What is the best way to validate attributes of a class that have many attributes in Python?

我在 API 中工作,我必须验证用户在 post/put 端点中写入的数据。但是我的模型 class 有 10 多个属性,所以我必须用 10 个 ifs 来验证它们,所以这让我的代码看起来很糟糕。

示例:

class MyClass():

    atr1: str
    atr2: str
    atr3: str
    atr4: str
    atr5: str
    atr6: str
    atr7: str
    atr8: str
    atr9: str
    atr10: str

然后我有我的放置端点:

@api.put("/update/{id}")
def update(id: int, exemple: MyClass):
    if exemple not in exemples:
        return {"Error": "exemple not found!"}

    if exemple.atr1 != None:
        exemples[exemple].atr1 = exemple.atr1
    if exemple.atr2 != None:
        exemples[exemple].atr2 = exemple.atr2
    if exemple.atr3 != None:
        exemples[exemple].atr3 = exemple.atr3
    if exemple.atr4 != None:
        exemples[exemple].atr4 = exemple.atr4
    if exemple.atr5 != None:
        exemples[exemple].atr5 = exemple.atr5
    if exemple.atr6 != None:
        exemples[exemple].atr6 = exemple.atr6
    if exemple.atr7 != None:
        exemples[exemple].atr7 = exemple.atr7
    if exemple.atr8 != None:
        exemples[exemple].atr8 = exemple.atr8
    if exemple.atr9 != None:
        exemples[exemple].atr9 = exemple.atr9
    if exemple.atr10 != None:
        exemples[exemple].atr10 = exemple.atr10

    return exemples[exemple]

我知道我可以在另一个地方做这个验证,但我仍然需要做 10 个 ifs 来验证每个字段。我正在寻找一种方法来获取每个属性的名称,然后在 for 循环内的 if 语句中使用 for 和 validade 循环。我正在寻找方法来做到这一点。我找到了一种使用 __dict__, vars(MyClass) 的方法,但我无法让它工作。任何人都可以帮助我以简单的方式做到这一点吗?

我可以建议两种方法来执行此操作并避免这些 if else 块。

首先: 您可以使用属性。对于 class 中的每个属性,添加一个 属性(getter 和 setter)。这样,每次从任何地方将新值分配给 class 的每个属性时,您都会进行验证。 (您应该牢记的一个重要注意事项是 I/O 任务中的属性很慢)。

class MyClass:
    _atr1: float

    @property
    def atr1(self) -> float:
        """
        This is getter method and just return the
       _atr1 attribute.
        """
        return self._atr1

    @atr1.setter
    def atr1(self, value) -> None:
        """
        This is setter method for _atr1 and has a
        little validation. If validation will not pass
        It will raise an ValueError.
        """
        if value > 0:
            self._atr1 = value
            return
        raise ValueError("The value is not enough!")

秒: 您可以使用描述符。通过这种方式,您可以在 class 之外添加许多描述符,并将这些描述符分配给您的属性。描述符相对于属性的一个重要优势是您可以在许多 classes 中使用它们并且它们具有更快的速度。

class NumberDescriptor(object):
    """
    This class is a descriptor which validate the value
    to be greater than 0 
    """

    def __init__(self, number=None):
        self.number = number

    def __get__(self, obj, objtype):
        return self.number

    def __set__(self, obj, number):
        if number > 0:
            self.number = number
        else:
            raise ValueError("The value is not enough!")


class MyClass:
    atr1 = NumberDescriptor()
    atr2 = NumberDescriptor()        
    atr3 = NumberDescriptor()


c = MyClass()
c.atr1 = 0

ValueError: The value is not enough!

您可以使用 Pydantic 验证器,它们可以很好地与 FastAPI 配合使用,并且更易于阅读或维护:

from pydantic import BaseModel, validator
class MyClass(BaseModel):
    attr1: str
    attr2: str
    [..]
    @validator('attr1')
    def attr1_validations(cls, v, values):
        # example (v is attr1)

        if len(v) > 255:
            raise ValueError(f"Length cannot be greater than 255!")
        elif v < 1:
            raise ValueError("Length cannot be lesser than 1!!")

        return v

但是,如果您只想检查输入中是否给出了所有字段并检查它们的长度,则可以使用 constr

from pydantic import BaseModel, validator, constr
class MyClass(BaseModel):
    attr1: constr(min_length=1, max_length=64)
    attr2: constr(min_length=1, max_length=128)
    [..]

请注意,Pydantic 模型中列出的所有字段都必须在向 FastAPI 服务器发出的请求中,否则将给出自动响应,提示输入无效。 如果你希望有可选字段,你可以这样做:

from pydantic import BaseModel, constr
from typing import Optional
class MyClass(BaseModel):
    attr1: constr(min_length=1, max_length=64)
    attr2: Optional[constr(min_length=1, max_length=64)] # this field can be skipped in the request body

请仔细阅读 FastAPI 和 Pydantic 的文档,它们提供了所有需要的信息: