装饰算术运算符|我应该使用元类吗?

时间:2010-07-07 00:26:55

标签: python metaclass

我想实现一个对象,它在对算术运算应用后限定给定范围内的值。下面的代码工作正常,但我毫无意义地重写了方法。当然,有一个更优雅的方式来做到这一点。元类是一种可行的方法吗?

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

2 个答案:

答案 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实例,而不是floatint s。