pythonnumpypython-internalspython-datamodel

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.)


Solution

  • An __req__ may have been considered more confusing than useful in the language. Consider 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() (and we would presumably like the result to be equivalent, either way). They can't both win.

    However, the Python datamodel does allow a way to do what you want here. The comparison can be controlled from either side of the operation, but you'll need to define AnyOf in a particular way. To control the eq from the right hand side of a comparison when the left side is an np.ndarray instance, AnyOf should 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

    Actually, no, there's a fundamental misunderstanding evident here. The left side always gets first try at handling the equality comparison, unless the right side type is a subclass of the left side type.

    arr == AnyOf(2,3)
    

    In the comparison shown 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 comparison shown above, your class does get the first try at the comparison, and it fails because of the way in was used - return other in self.elements is checking if an array is in a tuple.