验证类实例属性的正确方法
有一个简单的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