我想实现一个对象,它在对算术运算应用后限定给定范围内的值。下面的代码工作正常,但我毫无意义地重写了方法。当然,有一个更优雅的方式来做到这一点。元类是一种可行的方法吗?
def check_range(_operator):
def decorator1(instance,_val):
value = _operator(instance,_val)
if value > instance._upperbound:
value = instance._upperbound
if value < instance._lowerbound:
value = instance._lowerbound
instance.value = value
return Range(value, instance._lowerbound, instance._upperbound)
return decorator1
class Range(object):
'''
however you add, multiply or divide, it will always stay within boundaries
'''
def __init__(self, value, lowerbound, upperbound):
'''
@param lowerbound:
@param upperbound:
'''
self._lowerbound = lowerbound
self._upperbound = upperbound
self.value = value
def init(self):
'''
set a random value within bounds
'''
self.value = random.uniform(self._lowerbound, self._upperbound)
def __str__(self):
return self.__repr__()
def __repr__(self):
return "<Range: %s>" % (self.value)
@check_range
def __mul__(self, other):
return self.value * other
@check_range
def __div__(self, other):
return self.value / float(other)
def __truediv__(self, other):
return self.div(other)
@check_range
def __add__(self, other):
return self.value + other
@check_range
def __sub__(self, other):
return self.value - other
答案 0 :(得分:2)
可以使用元类将装饰器应用于一组函数名称,但我不认为这是你的情况。使用@decorator
语法在逐个函数的基础上在类体中应用装饰器,我认为是一个非常好的选择。 (我认为你的装饰器中有一个bug,BTW:你可能不想将instance.value
设置为任何东西;算术运算符通常不会改变它们的操作数。)
我可能会在你的情况下使用的另一种方法,就是一起避免装饰器,就是做这样的事情:
import operator
class Range(object):
def __init__(self, value, lowerbound, upperbound):
self._lowerbound = lowerbound
self._upperbound = upperbound
self.value = value
def __repr__(self):
return "<Range: %s>" % (self.value)
def _from_value(self, val):
val = max(min(val, self._upperbound), self._lowerbound)
# NOTE: it's nice to use type(self) instead of writing the class
# name explicitly; it then continues to work if you change the
# class name, or use a subclass
return type(self)(val, rng._lowerbound, rng._upperbound)
def _make_binary_method(fn):
# this is NOT a method, just a helper function that is used
# while the class body is being evaluated
def bin_op(self, other):
return self._from_value(fn(self.value, other))
return bin_op
__mul__ = _make_binary_method(operator.mul)
__div__ = _make_binary_method(operator.truediv)
__truediv__ = __div__
__add__ = _make_binary_method(operator.add)
__sub__ = _make_binary_method(operator.sub)
rng = Range(7, 0, 10)
print rng + 5
print rng * 50
print rng - 10
print rng / 100
印刷
<Range: 10>
<Range: 10>
<Range: 0>
<Range: 0.07>
我建议你不要在这种情况下使用元类,但这是你可以采用的一种方法。元类是一个有用的工具,如果你感兴趣,很高兴理解如何在你真正需要它们的时候使用它们。
def check_range(fn):
def wrapper(self, other):
value = fn(self, other)
value = max(min(value, self._upperbound), self._lowerbound)
return type(self)(value, self._lowerbound, self._upperbound)
return wrapper
class ApplyDecoratorsType(type):
def __init__(cls, name, bases, attrs):
for decorator, names in attrs.get('_auto_decorate', ()):
for name in names:
fn = attrs.get(name, None)
if fn is not None:
setattr(cls, name, decorator(fn))
class Range(object):
__metaclass__ = ApplyDecoratorsType
_auto_decorate = (
(check_range,
'__mul__ __div__ __truediv__ __add__ __sub__'.split()),
)
def __init__(self, value, lowerbound, upperbound):
self._lowerbound = lowerbound
self._upperbound = upperbound
self.value = value
def __repr__(self):
return "<Range: %s>" % (self.value)
def __mul__(self, other):
return self.value * other
def __div__(self, other):
return self.value / float(other)
def __truediv__(self, other):
return self / other
def __add__(self, other):
return self.value + other
def __sub__(self, other):
return self.value - other
答案 1 :(得分:1)
明智地谈到元类: 如果你想知道你需要它们,那么你就不会 。
我不完全理解你的问题,但是我会创建一个BoundedValue
类,而我们只将所述类的实例添加到你提议的类中。
class BoundedValue(object):
default_lower = 0
default_upper = 1
def __init__(self, upper=None, lower=None):
self.upper = upper or BoundedValue.default_upper
self.lower = lower or BoundedValue.default_lower
@property
def val(self):
return self._val
@val.setter
def val(self, value):
assert self.lower <= value <= self.upper
self._val = value
v = BoundedValue()
v.val = 0.5 # Correctly assigns the value 0.5
print v.val # prints 0.5
v.val = 10 # Throws assertion error
当然,您可以(并且应该)更改assert
离子以查找您正在寻找的实际行为;您也可以更改构造函数以包含初始化值。我选择通过属性val
进行构建后的作业。
拥有此对象后,您可以创建类并使用BoundedValue实例,而不是float
或int
s。