【问题标题】:Decorating arithmetic operators | should I be using a metaclass?装饰算术运算符 |我应该使用元类吗?
【发布时间】:2010-07-07 00:26:54
【问题描述】:

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

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

【问题讨论】:

    标签: python metaclass


    【解决方案1】:

    可以使用元类将装饰器应用于一组函数名称,但我认为这不是您的情况。正如您所做的那样,使用@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
    

    【讨论】:

    • 我认为这最清楚地回答了 OPs 的问题,但我认为另一个答案对这个问题有更好的解决方案。
    • 哇,非常感谢马特。我同意你既不使用装饰器也不使用元类的解决方案是最干净的。非常感谢你向我展示了类型(自我)的想法以及如何自动应用装饰器。非常有指导意义,非常感谢!
    • 关于有趣的 type(self) 模式的问题:class RRRange(Range): def __init__(self, *args): Range.__init__(self, *args) def whatsmytype(self): return type( self ) rng = RRRange(7, 0, 10) print rng + 5 # 返回 Range 或 RRRange 对象 type(self)(val, rng._lowerbound, rng._upperbound) 与 Range(self)(val) 有何不同,rng._lowerbound,rng._upperbound)?想一想,如果不必重新定义算术运算符,元类方法会更有意义。我很好奇这是否可行?
    • 它确实返回了一个RRRange 对象。将您的 __repr__() 方法主体更改为 return "&lt;%s(%r)&gt;" % (type(self).__name__, self.value),以便更容易查看。更好的是,将其更改为return "%s(%r, %r, %r)" % (type(self).__name__, self.value, self._lowerbound, self._upperbound)
    【解决方案2】:

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

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

    当然,您可以(并且应该)将assertion 更改为您正在寻找的实际行为;您也可以更改构造函数以包含初始化值。我选择通过属性val 将其作为构建后的分配。

    一旦你有了这个对象,你就可以创建你的类并使用 BoundedValue 实例,而不是 floats 或 ints。

    【讨论】:

    • 使用属性可能是个好主意。但是,我真正好奇的是如何将装饰器应用于算术运算符?我想我在这些行中寻找更多的模式: class Range(object) self.__new__( self ): for i in [self.__mul__, self.__add__, ...]: i = check_range(i)
    猜你喜欢
    • 2010-12-11
    • 1970-01-01
    • 2010-09-08
    • 2019-05-13
    • 1970-01-01
    • 2022-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多