2010-07-07 4 views
3

Je voudrais implémenter un objet, qui limite les valeurs dans une plage donnée après que les opérations arithmétiques lui ont été appliquées. Le code ci-dessous fonctionne bien, mais je suis en train de réécrire inutilement les méthodes. Il y a sûrement une façon plus élégante de le faire. Une métaclasse est-elle le chemin à parcourir?Opérateurs arithmétiques de décoration | devrais-je utiliser une métaclasse?

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 

Répondre

2

Il est possible d'utiliser une métaclasse pour appliquer un décorateur à un ensemble de noms de fonctions, mais je ne pense pas que ce soit le chemin à parcourir dans votre cas. Appliquer le décorateur dans le corps de la classe sur une base fonction par fonction que vous avez fait, avec la syntaxe @decorator, je pense est une très bonne option. (Je pense que vous avez un bug dans votre décorateur, BTW: vous ne voulez probablement pas mettre instance.value à quoi que ce soit, les opérateurs arithmétiques ne font généralement pas muter leurs opérandes).

Une autre approche que je pourrais utiliser dans votre situation, sorte d'éviter les décorateurs tous ensemble, est de faire quelque chose comme ceci:

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 

impression

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

Je suggère que vous n'utilisez pas un métaclasse dans cette circonstance, mais voici une façon que vous pourriez. Les métaclasses sont un outil utile, et si cela vous intéresse, c'est bien de comprendre comment les utiliser quand vous en avez vraiment besoin.

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 
+0

Je pense que cela répond le plus clairement à la question OPs, mais je pense que l'autre réponse apporte une solution légèrement meilleure au problème. – Omnifarious

+0

Wow, merci beaucoup Matt. Je suis d'accord que la solution où vous n'utilisez ni décorateurs ni métaclasses est la plus propre. Merci beaucoup de me montrer le type (auto) idée et comment appliquer les décorateurs automagiquement. Très instructif, merci beaucoup! – Jelle

+0

Une question sur le type intéressant (auto) modèle: classe RRRange (Plage): def __init __ (self, * args): Range.__init __ (self, * args) def whatsmytype (auto): type de retour (auto) RNG = RRRange (7, 0, 10) impression RNG + 5 # retourne une plage, ni objet RRRange Comment est le type (self) (val, rng._lowerbound, rng._upperbound) différent de Plage (auto) (val, rng._lowerbound, rng._upperbound)? En y réfléchissant, l'approche de la métaclasse aurait eu plus de sens s'il n'était pas nécessaire de redéfinir l'opérateur arithmétique. Je suis curieux de savoir si c'est faisable? – Jelle

1

Comme il est dit avec sagesse: métaclasses à propos si vous vous demandez wether vous en avez besoin, vous n'avez pas.

Je ne comprends pas parfaitement votre problème, mais je créerais une classe BoundedValue, et nous ne ferions que des occurrences de cette classe dans la classe que vous proposez.

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 

Bien sûr, vous pourrait (et devrait) changer l'ion assert pour le comportement réel que vous recherchez; Vous pouvez également modifier le constructeur pour inclure la valeur d'initialisation. J'ai choisi d'en faire une cession post-construction via la propriété val. Une fois que vous avez cet objet, vous pouvez créer vos classes et utiliser les instances BoundedValue au lieu de float s ou int s.

+0

L'utilisation d'une propriété est probablement une bonne idée. Cependant, ce que je suis vraiment curieux de savoir comment je peux appliquer le décorateur aux opérateurs arithmétiques? Je suppose que je cherche un motif plus de quelque chose parmi ces lignes: class Range (objet) self .__ nouveau __ (self): pour moi dans [self .__ mul__, self .__ add__, ...]: i = check_range (i) – Jelle

Questions connexes