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

107 投票
6 回答
64586 浏览
提问于 2025-04-15 22:39

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

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

我想检查以下几个条件:

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

我应该怎么做呢?
1. 在创建这个对象之前先验证数据吗?
2. 在__init__方法中检查数据吗?
3. 在Spam类中创建一个is_valid方法,然后用spam.isValid()来调用它吗?
4. 在Spam类中创建一个is_valid静态方法,然后用Spam.isValid(description, value)来调用它吗?
5. 在设置器声明中检查数据吗?
6. 还有其他方法吗?

你能推荐一种设计良好、符合Python风格、不冗长(对于有很多属性的类)、优雅的方法吗?

6 个回答

7

如果你不想自己动手做,可以直接使用 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__ 方法中进行检查(并通过描述符、装饰器或 metaclass 让这些检查完全透明),但我个人不太喜欢这样。我更喜欢用户输入和内部对象之间有一个干净的隔离。

12

如果你只想在创建对象的时候检查值是否有效,并且传入无效值被认为是编程错误,那么我建议使用断言:

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

这样写是最简洁的方式了,而且清楚地说明了这些是创建对象的前提条件。

133

你可以使用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

如果有人试图违反这些规则,就会抛出一个异常,甚至在__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

撰写回答