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

I have a small helper class:

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

This allows me to do sweet magic like:

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

without using list comprehension (as in np.array(x in (2,3) for x in arr ).

(I support a user interface that allows (trusted) users to enter arbitrary code, and a == AnyOf(1,2,3) much more enjoyable than list comprehension for a non-technical user.)

But!

It only works in one direction! For example, if I were to do AnyOf(2,3) == arr , then the AnyOf class __eq__ would never be called: instead, the NumPy array __eq__ method is __eq__ , which inside (I would assume) calls the __eq__ method of all its elements.

This made me wonder: why doesn't Python allow the right- __eq__ equivalent of __eq__ ? (Roughly equivalent to methods like __radd__ , __rmul__ , etc.)

+5
source share
3 answers

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

However, the Python datamodel allows you to do what you want here. On both sides you can control this comparison, but you need to correctly determine AnyOf . If you want AnyOf control __eq__ on the right side, you must define it as a subclass of np.ndarray .

if I had to do AnyOf(2,3) == arr , then my AnyOf class __eq__ will never be called

No, you have a fundamental misunderstanding. The left side always first checks for equality comparisons if the right side is not a subclass of the left side type.

 arr == AnyOf(2,3) 

In the above example, your custom __eq__ because the numpy array calls it! So np.ndarray wins and it decides to check once on an element. He could literally do something else, including not calling your AnyOf.__eq__ at all.

 AnyOf(2,3) == arr 

In the above example, your class makes a first comparison attempt, and it fails due to how you used in (checking if there is an array in the tuple).

+5
source

documentation of __rxx__ methods like __radd__ :

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

While classes do not have __add__ or __sub__ by default, they do have __eq__ :

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

This means that __req__ will never be called unless you explicitly remove __eq__ from another class.

You can solve your specific problem with np.in1d :

 >>> np.in1d(arr, [2, 3]) array([False, True, True, False, False], dtype=bool) 
+2
source

This is the documentation in the data model :

There are no versions of these methods with replaced arguments (for use when the left argument does not support the operation, but the right argument); rather, __lt__() and __gt__() are each other's reflection, __le__() and __ge__() are each other's reflection, and __eq__() and __ne__() are their own reflection. If the operands have different types, and the right type of operands is a direct or indirect subclass of the type of left operands, the reflected right operand method takes precedence, otherwise the left operand method takes precedence. Virtual subclassing is not considered.

As stated in the comments above, what you want works, and __eq__ is essentially the sames as potential __req__ : it is called on the right side == if the object on the left returns NotImplemented :

 In [1]: class A: ...: def __eq__(self, other): ...: return NotImplemented ...: In [2]: class B: ...: def __eq__(self, other): ...: print("B comparing") ...: return True ...: In [3]: B() == A() B comparing Out[3]: True In [4]: A() == B() B comparing Out[4]: True In [5]: A() == A() Out[5]: False 

As it arrives, it even works with other, ordinary objects:

 In [10]: 5 == B() B comparing Out[10]: True 

However, some objects may throw a TypeError on __eq__ instead of returning NotImplemented or False , and this makes this unreliable for all kinds of objects.

What happens in your case is the misuse of the in operator with arrays and tuples inside your own __eq__ method. (Thanks @wim for noticing this in another answer here).

+1
source

Source: https://habr.com/ru/post/1273768/


All Articles