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

sys*_*out 72 python

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

class Spam(object):
    __init__(self, description, value):
        self.description = description
        self.value = value
Run Code Online (Sandbox Code Playgroud)

我想检查以下约束:

  • "描述不能为空"
  • "价值必须大于零"

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

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

Mar*_*tos 90

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

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
Run Code Online (Sandbox Code Playgroud)

任何违反规则的行为都会抛出异常,即使在__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
Run Code Online (Sandbox Code Playgroud)

  • @JohnBensin:是的,不.`self.description = ...`通过属性赋值,而`self._description = ...`直接赋值给底层字段.在施工期间使用哪一个是设计选择,但通常通过财产分配通常更安全.例如,如果您调用`Spam('',1)`,上面的代码将引发异常. (12认同)
  • 同意,这不是最漂亮的解决方案.Python更喜欢自由范围的类(想想鸡),控制访问的属性的想法是一个事后的想法.话虽如此,我能想到的任何其他语言都不会简洁得多. (2认同)
  • @MarceloCantos我意识到这是一个老问题,但基于[文档](http://docs.python.org/3/library/functions.html#property)(虽然对于Python 3),应该是`self.description = description`使用下划线,即`self._description = description`,或者这不重要吗?这是必要的还是仅仅类似于Python的"私有"变量版本? (2认同)

Dav*_*rby 9

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

class Spam(object):
    def __init__(self, description, value):
        assert description != ""
        assert value > 0
        self.description = description
        self.value = value
Run Code Online (Sandbox Code Playgroud)

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

  • 您可以在断言语句中添加一条消息,例如`assert value > 0,“垃圾邮件的值属性必须大于零”`。断言实际上是给开发人员的消息,不应被客户端代码捕获,因为它们表示编程错误。如果您希望客户端捕获并处理错误,则显式引发异常,例如 ValueError,如其他答案所示。 (2认同)

Joc*_*zel 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) 
Run Code Online (Sandbox Code Playgroud)

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


Sil*_*ost 5

如果您只想验证传递给构造函数的那些值,则可以执行以下操作:

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

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

>>> s = Spam('s', 5)
>>> s.value = 0
>>> s.value
0
Run Code Online (Sandbox Code Playgroud)

因此,正确的方法取决于您要完成的工作。

  • @system:您可以将有效性检查分成自己的方法:这种情况没有硬性规定。 (2认同)