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

发布于 2024-09-08 14:00:21 字数 1564 浏览 2 评论 0原文

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

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

像极了他 2024-09-15 14:00:21

可以使用元类将装饰器应用于一组函数名称,但我认为这不是您的情况的方法。正如您所做的那样,使用 @decorator 语法在类主体中逐个函数地应用装饰器,我认为这是一个非常好的选择。 (我认为你的装饰器中有一个错误,顺便说一句:你可能不想将 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

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
贱贱哒 2024-09-15 14:00:21

正如关于元类的明智说法:如果您想知道是否需要它们,那么您就不需要

我不完全理解你的问题,但我会创建一个 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

当然,您可以(并且应该)更改您正在寻找的实际行为的断言;您还可以更改构造函数以包含初始化值。我选择通过属性 val 将其设置为构造后赋值。

拥有此对象后,您可以创建类并使用 BoundedValue 实例,而不是 floatint

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文