Can I get the name of the operator that triggered a special method?

97 views Asked by At

I am working in customizing the behaviour of Python native functions and operators in my classes. I would like to avoid hard coding when printing error messages like "'<' not supported between instances of 'int' and 'str'". I know how to avoid hard coding of type names (by using type(object).__name__. But ¿how can I refer to the native function or operator that triggered my customized special method?

Here it comes an easy example:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def __lt__(self, other):
        if not isinstance(other, Person):
            raise TypeError("'<' not supported between instances of "
                            f"'{type(self).__name__}'"
                            f" and '{type(other).__name__}'")
        else:
            return self.age < other.age
        
    def __ge__(self, other):
        return not self < other

With this class definition I have:

>>> me = Person('Javier', 55)
>>> you = Person('James', 25)
>>> print(you < me)
True
>>> print(you >= me)
False
>>> print(you < 30)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "....py", line 8, in __lt__
    raise TypeError("'<' not supported between instances of "
TypeError: '<' not supported between instances of 'Person' and 'int'
>>> print(you >= 30)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "....py", line 15, in __ge__
    return not self < other
  File "....py", line 8, in __lt__
    raise TypeError("'<' not supported between instances of "
TypeError: '<' not supported between instances of 'Person' and 'int'
>>> 

As you can see, I had to hardcode the name of the operator '<'. I usually avoid hard coding, but in this case I have an additional reason to avoid it: the TypeError message in operator >= is wrong as it says: '<' not supported between instances of 'Person' and 'int'

2

There are 2 answers

1
D.L On

you could achieve this by defining an operator variable and then setting it to whatever it should be based on the conditions.

So something like this:

x = 10

if x < 10: operator = "<"
elif x > 10: operator = ">"
else: operator = "=" 

print(f'you have used the {operator} operator')

You would need to pass the equivalent of this into whatever classes you decide to use this way.

7
danz On

What you're asking here is 'How to get the method name from within the method so I can use to print it in a variable?'

This has been answered a bit here https://stackoverflow.com/a/5067654/5005360

As the reference shows, the inspect.stack()[0][3] gets the name of the method that was just called.

First you need to map that for the method __lt__ (and the others as well) you actually are using the '<' operator.

map_compar = {'__lt__' : '<', '__gt__': '>', '__eq__': '=='}

Then you create your Person class with your implementation (not included here)

class Person:
     def __lt__(self, value):

             #Gets the function name
             fn_name = inspect.stack()[0][3] 
             
             #Prints fn name with correct mapped operator
             print(f'You used the {map_compar[fn_name]} operator') 

     def __gt__(self, value):
             fn_name = inspect.stack()[0][3]
             print(f'You used the {map_compar[fn_name]} operator')

     def __eq__(self, value):
             fn_name = inspect.stack()[0][3]
             print(f'You used the {map_compar[fn_name]} operator')

And finally using it:

>>> person = Person()
>>> person < 10
You used the < operator
>>> person > 10
You used the > operator
>>> person == 10
You used the == operator

PS: This looks way too complicated of a solution for whatever you're doing. If it's just to prove a point and manipulate a class, fine, but if it's for something professional, you should think for a second if this is the best strategy.