0

I have a class Foo which contains one of math comparison operations op as a string: '<', '>' or '='. Also Foo contains a number val.

class Foo:
  def __init__(self, operation: str, value: int):
    self.op = operation
    self.val = value

In my code I read a number from input and compare it to Foo's class exemplar value using Foo's contained operation:

f = Foo('<', 15)
to_check = int(input())

if f.op == '<':
  if to_check < f.val:
    # ...
elif f.op == '>':
  if to_check > f.val:
    # ...
elif f.op == '=':
  if to_check == f.val:
    #...  

Is there any way to do it simpler or more elegant?

elo
  • 489
  • 3
  • 13
  • You might be able to use ast.literal_eval to evaluate it as a string: https://stackoverflow.com/questions/15197673/using-pythons-eval-vs-ast-literal-eval – William Jun 06 '21 at 17:47

5 Answers5

3

This is more elegant and works just fine :

class Foo:

    def __init__(self, op: str, val: int):
        self.op = op
        self.val = val

    def check(self, other):
        if isinstance(other, (int, float)):
            if self.op == '<': return self.val > other
            if self.op == '>': return self.val < other
            if self.op == '=': return self.val == other
            if self.op == '!=': return self.val != other
            if self.op == '>=': return self.val <= other
            if self.op == '<=': return self.val >= other
        return NotImplemented

f.check(other) check the condition of the contained operation and returns it, in other words:

f = Foo('<', 15)
if f.check(14):
    print('ok')

The condition is True because 14 is less than 15

S-c-r-a-t-c-h-y
  • 321
  • 2
  • 5
  • There is sometimes an aversion to multiple return statements (though no consensus: https://stackoverflow.com/questions/31992052/python-coding-style-multiple-return-statements). However this gets extra points for readability. – William Jun 06 '21 at 18:02
  • 1. `if isinstance(other, int) or isinstance(other, float):` can be reduced to `if isinstance(other, (int, float)):` - will against each type given. 2. If `val` is not an int or float, don't return `False`, it should [`return NotImplemented`](https://docs.python.org/3/library/constants.html#NotImplemented). – aneroid Jun 06 '21 at 18:27
  • Tanks for letting me know ! I didn't know that `NotImplemented` was a thing. I fixed it – S-c-r-a-t-c-h-y Jun 06 '21 at 19:06
2

An alternative: If I were using the Foo class in a program, I think I would use @S-c-r-a-t-c-h-y's approach. But here is a perhaps elegant approach using the functional form of comparison operators.

class Foo:
    def __init__(self, operation: str, value: int):
        self.op = operation
        self.val = value

f = Foo('<', 15)
to_check = int(12)

import operator
op_dict = {
    '>': operator.gt,
    '<': operator.lt,
    '==':operator.eq
}

op_dict.get(f.op)(to_check, f.val)
William
  • 381
  • 1
  • 8
2

Thanks for answering, but I also considered to store operation not like a string, but a lambda. It seems to be not a bad solution for my case:

less = lambda x, y: x < y
equal = lambda x, y: x == y

f = Foo(less, 15)
to_check = 12

if f.op(to_check, f.val):
  #...
elo
  • 489
  • 3
  • 13
  • This is a good approach also. You could make it simpler by using `operator.gt` and `operator.eq` instead of making them with lambdas. – William Jun 06 '21 at 18:55
1

This will work with eval. But caution must be used with eval! See this post for a safer way by first parsing with ast.literal_eval. test a boolean expression in a Python string

The safer approach might not count any longer as "simpler" or "more elegant".

class Foo:
    def __init__(self, operation: str, value: int):
        self.op = operation
        self.val = value

f = Foo('<', 15)
to_check = int(input())

eval(f'{to_check} {f.op} {f.val}')
William
  • 381
  • 1
  • 8
1

Python has built in to do that. We can override them


class Foo:
    def __init__(self, operation: str, value: int):
        self.op = operation
        self.value = value
    def __repr__(self):
        
        return f"{self.__class__.__name__}({self.value} {self.op} input_X )"

    def __lt__(self, input_value):
        return self.value < input_value

    def __le__(self, input_value):
        return self.value <= input_value
    
    def __eq__(self, input_value):
        return self.value == input_value
    
    def __gt__(self, input_value):
        return self.value > input_value
    
    def __ge__(self, input_value):
        return self.value >= input_value
    
    def check(self, input_value:int):
        
        operations = {'<': self.__lt__,
                     '<=': self.__le__,
                     '==': self.__eq__,
                     '>': self.__gt__,
                     '>=':self.__ge__}
        
        return operations.get(self.op)( input_value)

demo


f = Foo(">=", 15)
print(f)
# Foo(15 >= input_X )

f.check(14)
# True
f.check(16)
# False

f > 16 
# False

f > 14
# True
Prayson W. Daniel
  • 14,191
  • 4
  • 51
  • 57
  • 1
    Clever approach. Though I think this is a little bit of a mangling of the builtins like `__lt__`. Those would be more appropriate if we were comparing different `Foo` instantiations like `f1 = Foo(); f2 = Foo(); f1 > f2`. In this case I would just make those private functions like `def _less_than` or use the `operators.lt`. – William Jun 06 '21 at 18:58
  • Now, I fixed ;) but it is a hack still – Prayson W. Daniel Jun 06 '21 at 19:16