Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't Python have a "__req__" (reflected equality) method?

I have a little helper class:

class AnyOf(object):
    def __init__(self, *args):
        self.elements = args
    def __eq__(self, other):
        return other in self.elements

This lets me do sweet magic like:

>>> arr = np.array([1,2,3,4,5])
>>> arr == AnyOf(2,3)
np.array([False, True, True, False, False])

without having to use a list comprehension (as in np.array(x in (2,3) for x in arr).

(I maintain a UI that lets (trusted) users type in arbitrary code, and a == AnyOf(1,2,3) is a lot more palatable than a list comprehension to the non-technically savvy user.)

However!

This only works one way! For example, if I were to do AnyOf(2,3) == arr then my AnyOf class's __eq__ method never gets called: instead, the NumPy array's __eq__ method gets called, which internally (I would presume) calls the __eq__ method of all its elements.

This lead me to wonder: why does Python not allow a right-sided equivalent to __eq__? (Roughly equivalent to methods like __radd__, __rmul__, et cetera.)

like image 688
acdr Avatar asked Nov 30 '17 15:11

acdr


2 Answers

An __req__ is not a good idea in the language, because if class Left defines __eq__ and class Right defines __req__, then Python is obliged to make a consistent decision about who gets called first in Left() == Right(). They can't both win.

However, the Python datamodel does allow a way for you to do what you want here. From both sides you can control this comparison, but you'll need to define AnyOf correctly. If you want AnyOf to control the __eq__ from the right hand side, you must define it to be a subclass of np.ndarray.

if I were to do AnyOf(2,3) == arr then my AnyOf class's __eq__ method never gets called

No, you have a fundamental misunderstanding here. The left hand side always gets first try at the equality comparison, unless the right hand side is a subclass of the type of the left hand side.

arr == AnyOf(2,3)

In the case above, your custom __eq__ is being called, because the numpy array calls it! So np.ndarray wins, and it decides to check once per element. It literally could do anything else, including not calling your AnyOf.__eq__ at all.

AnyOf(2,3) == arr

In the case above, your class does get the first try at the comparison, and it fails because of the way you used in (checking if an array is in a tuple).

like image 125
wim Avatar answered Nov 05 '22 18:11

wim


The documentation about the __rxx__ methods like __radd__ states:

These functions are only called if the left operand does not support the corresponding operation and the operands are of different types.

While classes don't have __add__ or __sub__ methods per default, they do have __eq__:

>>> class A(object):
...     pass
>>> '__eq__' in dir(A)
True

This means __req__ would never be called unless you explicitly remove __eq__ from the other class or make __eq__ return NotImplemented.

You can solve your specific problem with np.in1d:

>>> np.in1d(arr, [2, 3])
array([False,  True,  True, False, False], dtype=bool)
like image 27
Mike Müller Avatar answered Nov 05 '22 18:11

Mike Müller