开发者

Decorating arithmetic operators | should I be using a metaclass?

I'd like to implement an object, that bounds values within a given range after arithmetic operations have been applied to it. The code below works fine, but I'm pointlessly rewriting the methods. Surely there's a more elegant way of doing this. Is a metaclass the way to go?

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


It is possible to use a metaclass to apply a decorator to a set of function names, but I don't think that this is the way to go in your case. Applying the decorator in the class body on a function-by-function basis as you've done, with the @decorator syntax, I think is a very good option. (I think you've got a bug in your decorator, BTW: you probably do not want to set instance.value to anything; arithmetic operators usually don't mutate their operands).

Another approach I might use in your situation, kind of avoiding decorators all together, is to do something like this:

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

printing

<Range: 10>
<Range: 10>
<Range: 0>
<Range: 0.07>

I suggest that you do NOT use a metaclass in this circumstance, but here is one way you could. Metaclasses are a useful tool, and if you're interested, it's nice to understand how to use them for when you really need them.

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


As it is wisely said about metaclasses: if you wonder wether you need them, then you don't.

I don't fully understand your problem, but I would create a BoundedValue class, and us only instances of said class into the class you are proposing.

 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

Of course you could (and should) change the assertion for the actual behavior you are looking for; also you can change the constructor to include the initialization value. I chose to make it an assignment post-construction via the property val.

Once you have this object, you can create your classes and use BoundedValue instances, instead of floats or ints.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜