验证类实例属性的正确方法

时间:2010-05-13 08:53:17

标签: python

有一个像这样的简单Python类:

class Spam(object):
    __init__(self, description, value):
        self.description = description
        self.value = value

我想检查以下约束:

  • “描述不能为空”
  • “值必须大于零”

我应该:
1.在创建垃圾邮件对象之前验证数据? 2.检查__init__方法的数据?
3.在垃圾邮件类上创建一个is_valid方法并使用spam.isValid()调用它? 4.在垃圾邮件类上创建一个is_valid静态方法,并使用Spam.isValid(描述,值)调用它?
5.检查制定者声明的数据?
6.等等。

你能推荐精心设计的/ Pythonic /非冗长(具有多种属性的课程)/优雅的方法吗?

5 个答案:

答案 0 :(得分:82)

您可以使用Python properties分别将规则清晰地应用于每个字段,并在客户端代码尝试更改字段时强制执行它们:

class Spam(object):
    def __init__(self, description, value):
        self.description = description
        self.value = value

    @property
    def description(self):
        return self._description

    @description.setter
    def description(self, d):
        if not d: raise Exception("description cannot be empty")
        self._description = d

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, v):
        if not (v > 0): raise Exception("value must be greater than zero")
        self._value = v

即使在__init__函数中,任何违反规则的行为都会抛出异常,在这种情况下,对象构造将失败。

更新: 2010年至今的某个时间,我了解了operator.attrgetter

import operator

class Spam(object):
    def __init__(self, description, value):
        self.description = description
        self.value = value

    description = property(operator.attrgetter('_description'))

    @description.setter
    def description(self, d):
        if not d: raise Exception("description cannot be empty")
        self._description = d

    value = property(operator.attrgetter('_value'))

    @value.setter
    def value(self, v):
        if not (v > 0): raise Exception("value must be greater than zero")
        self._value = v

答案 1 :(得分:7)

如果您只想在创建对象时验证值,并且传入无效值被视为编程错误,那么我将使用断言:

class Spam(object):
    def __init__(self, description, value):
        assert description != ""
        assert value > 0
        self.description = description
        self.value = value

这与您将要获得的内容一样简洁,并且清楚地记录了这些是创建对象的先决条件。

答案 2 :(得分:6)

除非你自己动手,否则你可以简单地使用formencode。它确实闪耀着许多属性和模式(只是子类模式)并且内置了许多有用的验证器。正如您所看到的,这是“在创建垃圾邮件对象之前验证数据”方法。

from formencode import Schema, validators

class SpamSchema(Schema):
    description = validators.String(not_empty=True)
    value = validators.Int(min=0)

class Spam(object):
    def __init__(self, description, value):
        self.description = description
        self.value = value

## how you actually validate depends on your application
def validate_input( cls, schema, **input):
    data = schema.to_python(input) # validate `input` dict with the schema
    return cls(**data) # it validated here, else there was an exception

# returns a Spam object
validate_input( Spam, SpamSchema, description='this works', value=5) 

# raises an exception with all the invalid fields
validate_input( Spam, SpamSchema, description='', value=-1) 

你也可以在__init__期间进行检查(并使用描述符| decorators | metaclass使它们完全透明),但我不是那个人的忠实粉丝。我喜欢用户输入和内部对象之间的干净屏障。

答案 3 :(得分:5)

如果您只想验证传递给构造函数的值,您可以这样做:

class Spam(object):
    def __init__(self, description, value):
        if not description or value <=0:
            raise ValueError
        self.description = description
        self.value = value

这当然不会阻止任何人做这样的事情:

>>> s = Spam('s', 5)
>>> s.value = 0
>>> s.value
0

所以,正确的方法取决于你想要完成的事情。

答案 4 :(得分:0)

您可以尝试pyfields

from pyfields import field

class Spam(object):
    description = field(validators={"description can not be empty": lambda s: len(s) > 0})
    value = field(validators={"value must be greater than zero": lambda x: x > 0})

s = Spam()
s.description = "hello"
s.description = ""  # <-- raises error, see below

它产生

ValidationError[ValueError]: Error validating [<...>.Spam.description=''].
  InvalidValue: description can not be empty. 
  Function [<lambda>] returned [False] for value ''.

它兼容python 2和3.5(与pydantic相对),并且每次值更改时都会进行验证(不仅是第一次,与attrs相对)。它可以为您创建构造函数,但默认情况下不执行上述操作。

有关详细信息,请参见pyfields documentation(顺便说一下,我是作者;))