可重用的验证类属性

时间:2016-05-05 20:36:26

标签: python python-3.x

我有一个通过@property装饰器实现属性的类,如下所示

class num(object):

    def __init__(self, value):
        self.val = value

    @property
    def val(self):
        return self._val

    @val.setter
    def val(self, value):
        validation(self, (int, float), value, 0.0)

当要设置属性的值时,应该验证它。

def validation(obj, types, value, default):
    """Checks if the input provided for the attribute is valid."""
    try:
        if not isinstance(value, types):
            obj._val = default
            raise TypeError
        if value < 0:
            obj._val = default
            raise ValueError
        else:
            obj._val = float(value)
    except ValueError as e:
        print("Should be a positive number. Value set at ", default)
    except TypeError:
        print("Should be a number. Value set at ", default)

此代码适用于此特定情况,其中我只有一个先验已知属性val。我想使函数validation更通用,以便能够重用其他属性(例如任意索引)。我可以看到我的问题存在于obj._val中,我正在打破封装。

我想一个可能的解决方案是通过使用链接到属性的setter的方法装饰器,或者可以将额外的参数传递给validation以了解哪个属性必须验证。或者也许是采用完全不同的方法。

提前致谢!

3 个答案:

答案 0 :(得分:4)

将验证者留给仅验证。如果该值无效,则引发异常,最多对整数执行float转换。您的验证器对数字过于具体,因此请将其重命名(并将其他验证器用于其他类型):

def is_positive_number(value):
    """Checks if the input provided is an actual number, returns a float."""
    if not isinstance(value, types):
        raise TypeError('Not a number')
    if value < 0:
        raise ValueError('Must be a positive number')
    return float(value)

将属性设置为setter;它也可以决定默认值:

@val.setter
def val(self, value):
    self._val = 0.0  # default, in case validation fails
    self._val = validation(value)

现在验证器不再与特定属性紧密耦合。其他更复杂的验证器可以根据需要传递更多上下文。

请注意,错误处理(捕获验证异常并打印消息)应该在属性的外部进行。在验证器内打印将您的代码与特定的交互模式紧密耦合;例如,您现在无法将此代码用于Web界面或GUI。

答案 1 :(得分:3)

我想说在python中实现这一目标的最干净/可重用的方法之一是描述符。对于这种要求,java风格的get / set方法过于冗长。

以下是您的模型的样子 - 只需3行:

class Person(object):
    balance = Integer('_balance')
    age = PositiveInteger('_age')

一些场景:

person = Person()

person.balance = 0  # OK
person.balance = '0'  # OK
person.balance = 'wrong'  # TypeError

person.age = 30  # OK
person.age = '30'  # OK
person.age = -1  # ValueError
person.age = 'wrong'  # TypeError

描述符IntegerPositiveInteger

的实现
class Integer(object):
    def __init__(self, name, default=None):
        self.name = name
        self.default = default

    def __get__(self, instance, owner):
        return getattr(instance, self.name, self.default)

    def clean(self, value):
        if isinstance(value, int) or str(value).isdigit():
            return int(value)
        return value

    def __set__(self, instance, value):
        if isinstance(self.clean(value), int):
            setattr(instance, self.name, value)
        else:
            raise TypeError('`{}` not a valid integer'.format(value))


class PositiveInteger(Integer):
    def clean(self, value):
        x = super(PositiveInteger, self).clean(value)
        if isinstance(x, int) and x < 0:
            raise ValueError('`{}` is not a positive integer'.format(value))
        return x

答案 2 :(得分:1)

def val(self, value):
    self._val = validation((int, float), value, 0.0)

def validation(types, value, default):
    """Checks if the input provided for the attribute is valid."""
    if not isinstance(value, types):
        print("Should be a number. Value set at ", default)
    elif value < 0:
        print("Should be a positive number. Value set at ", default)
    else:
        return value
    return default

没有必要提出异常并立即抓住它们。立即打印很好。但是,使用它们会影响程序的流程,因此不需要else。由于我没有使用它们,我不仅需要else,我需要elif作为第二个块。

我喜欢减少重复,因此上面的代码就是如此。为了可读性和健壮性,最好执行以下操作之一:

if ...
    print ...
    return default
if ...
    print ...
    return default
# Optionally add an else
return value

if ...
    print ...
    result = default
elif ...
    print ...
    result = default
else:
    result = value
return result

或者如果你想让它更短一些,重用参数:

if ...
    print ...
    value = default
elif ...
    print ...
    value = default
return value

另一种简明的方法(存在类似的变化)是一次处理所有错误。除了使代码更短,这意味着用户可以一次性了解所有需求,并且只会遇到另一个问题而无法解决问题。

if not isinstance(value, types) or value < 0:
    print("Should be a positive number. Value set at ", default)
    return default
return value

如果你已经知道你有一个int或一个浮点数,你还不清楚为什么要转换为浮点数,所以我把它留了出来。

另请注意,您的原始功能并未真正使用default0的设置是硬编码的,这很糟糕。