【问题标题】:How to validate typing attributes in Python 3.7如何在 Python 3.7 中验证类型属性
【发布时间】:2019-01-15 03:26:32
【问题描述】:

我想在实例创建后验证类型是对还是错, 我尝试使用@dataclass 装饰器,但不允许我使用__init__ 方法,我也尝试使用自定义类类型

还按类型的顺序进行了一些验证(例如,如果是int,则field>0 或如果是str,则为干净的空格), 我可以使用 dict 来验证类型,但我想知道是否有办法以 Python 的方式进行

class Car(object):
    """ My class with many fields """

    color: str
    name: str
    wheels: int

    def __init__(self):
        """ Get the type of fields and validate """
        pass

【问题讨论】:

  • 相关:Validating complex types in dataclasses。如果您遇到问题是因为您的类型不是像strintfloat 这样的直截了当的东西,那么该帖子中的答案可能会有所帮助。

标签: python-3.x oop python-3.7


【解决方案1】:

使用pydantic。 在此示例中,字段 password1 仅被验证为字符串,而其他字段具有自定义验证器功能。

from pydantic import BaseModel, ValidationError, validator


class UserModel(BaseModel):
    name: str
    username: str
    password1: str
    password2: str

    @validator('name')
    def name_must_contain_space(cls, v):
        if ' ' not in v:
            raise ValueError('must contain a space')
        return v.title()

    @validator('password2')
    def passwords_match(cls, v, values, **kwargs):
        if 'password1' in values and v != values['password1']:
            raise ValueError('passwords do not match')
        return v

    @validator('username')
    def username_alphanumeric(cls, v):
        assert v.isalnum(), 'must be alphanumeric'
        return v


user = UserModel(
    name='samuel colvin',
    username='scolvin',
    password1='zxcvbn',
    password2='zxcvbn',
)
print(user)
#> name='Samuel Colvin' username='scolvin' password1='zxcvbn' password2='zxcvbn'

try:
    UserModel(
        name='samuel',
        username='scolvin',
        password1='zxcvbn',
        password2='zxcvbn2',
    )
except ValidationError as e:
    print(e)
    """
    2 validation errors for UserModel
    name
      must contain a space (type=value_error)
    password2
      passwords do not match (type=value_error)
    """

【讨论】:

    【解决方案2】:

    数据类不检查数据。但是我为数据类做了一个小的上层结构,你可以这样使用它:

    import json
    from dataclasses import dataclass
    
    from validated_dc import ValidatedDC
    
    
    @dataclass
    class Car(ValidatedDC):
        color: str
        name: str
        wheels: int
    
    
    # This string was received by api
    data = '{"color": "gray",  "name": "Delorean", "wheels": 4}'
    
    # Let's upload this json-string to the dictionary
    data = json.loads(data)
    
    car = Car(**data)
    assert car.get_errors() is None
    
    # Let's say the key "color" got the wrong value:
    data['color'] = 11111
    
    car = Car(**data)
    assert car.get_errors()
    print(car.get_errors())
    # {
    #     'color': [
    #         BasicValidationError(
    #             value_repr='11111', value_type=<class 'int'>,
    #             annotation=<class 'str'>, exception=None
    #         )
    #     ]
    # }
    
    # fix
    car.color = 'gray'
    # is_valid() - Starting validation of an already created instance
    # (if True returns, then there are no errors)
    assert car.is_valid()
    assert car.get_errors() is None
    

    验证DC:https://github.com/EvgeniyBurdin/validated_dc

    【讨论】:

      【解决方案3】:

      @dataclass 的替代方法是使用pyfields。它提供开箱即用的验证和转换,并且直接在字段级别完成,因此您可以在任何类中使用fields,而无需以任何方式修改它们。

      from pyfields import field, init_fields
      from valid8.validation_lib import is_in
      
      ALLOWED_COLORS = ('blue', 'yellow', 'brown')
      
      class Car(object):
          """ My class with many fields """
          color: str = field(check_type=True, validators=is_in(ALLOWED_COLORS))
          name: str = field(check_type=True, validators={'should be non-empty': lambda s: len(s) > 0})
          wheels: int = field(check_type=True, validators={'should be positive': lambda x: x > 0})
      
          @init_fields
          def __init__(self, msg="hello world!"):
              print(msg)
      
      c = Car(color='blue', name='roadie', wheels=3)
      
      c.wheels = 'hello'   # <-- (1) type validation error, see below
      c.wheels = 0         # <-- (2) value validation error, see below
      

      产生以下两个错误

      TypeError: Invalid value type provided for '<...>.Car.wheels'. 
         Value should be of type <class 'int'>. Instead, received a 'str': 'hello'
      

      valid8.entry_points.ValidationError[ValueError]: 
         Error validating [<...>.Car.wheels=0]. 
         InvalidValue: should be positive. 
         Function [<lambda>] returned [False] for value 0.
      

      详情请见pyfields documentation。顺便说一句,我是作者:)

      【讨论】:

        【解决方案4】:

        您可以使用数据类的__post_init__ 方法进行验证。

        下面我只是确认一切都是指定类型的实例

        from dataclasses import dataclass, fields
        
        def validate(instance):
            for field in fields(instance):
                attr = getattr(instance, field.name)
                if not isinstance(attr, field.type):
                    msg = "Field {0.name} is of type {1}, should be {0.type}".format(field, type(attr))
                    raise ValueError(msg)
        
        @dataclass
        class Car:
            color:  str
            name:   str
            wheels: int    
            def __post_init__(self):
                validate(self)
        

        【讨论】:

          猜你喜欢
          • 2020-02-01
          • 1970-01-01
          • 1970-01-01
          • 2014-01-28
          • 1970-01-01
          • 1970-01-01
          • 2014-02-10
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多