我有一个通过@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
以了解哪个属性必须验证。或者也许是采用完全不同的方法。
提前致谢!
答案 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
描述符Integer
和PositiveInteger
:
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或一个浮点数,你还不清楚为什么要转换为浮点数,所以我把它留了出来。
另请注意,您的原始功能并未真正使用default
,0
的设置是硬编码的,这很糟糕。