为什么 Python 没有“__req__”(反映相等性)方法?

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

我有个小帮手class:

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

这让我可以像这样施展甜蜜魔法:

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

无需使用列表理解(如 np.array(x in (2,3) for x in arr)。

(我维护了一个 UI 让(受信任的)用户输入任意代码,并且 a == AnyOf(1,2,3) 比非技术通晓的用户的列表理解更容易接受。)

但是!

这只有一种方法!例如,如果我要执行 AnyOf(2,3) == arr,那么我的 AnyOf class 的 __eq__ 方法永远不会被调用:相反,NumPy 数组的 __eq__ 方法被调用,它在内部(我假设)调用其所有元素的 __eq__ 方法。

这让我想知道:为什么 Python 不允许右侧等价于 __eq__? (大致等同于 __radd____rmul__ 等方法。)

documentation 关于像 __radd__ 这样的 __rxx__ 方法指出:

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

虽然 classes 默认没有 __add____sub__ 方法,但它们 __eq__

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

这意味着 __req__ 将永远不会被调用,除非您明确地从另一个 class 中删除 __eq__ 或使 __eq__ return NotImplemented .

您可以使用 np.in1d 解决您的特定问题:

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

这是关于 data model 的文档:

There are no swapped-argument versions of these methods (to be used when the left argument does not support the operation but the right argument does); 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 are of different types, and right operand’s type is a direct or indirect subclass of the left operand’s type, the reflected method of the right operand has priority, otherwise the left operand’s method has priority. Virtual subclassing is not considered.

正如上面的评论所述,你想要的是什么,__eq__ 本质上与潜在的 __req__ 相同:它在 == 的右侧调用如果左侧的对象 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

它甚至可以与其他普通对象一起使用:

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

但是,某些对象可能会在 __eq__ 上产生 TypeError 而不是返回 NotImplementedFalse,这使得这对所有类型的对象都不可靠。

在您的案例中发生的情况是在您自己的 __eq__ 方法中对数组和元组错误地使用了运算符 in。 (感谢@wim 在这里的另一个答案中发现了这一点)。

__req__ 在语言中不是一个好主意,因为如果 class Left 定义 __eq__ 而 class Right 定义__req__,那么 Python 必须就 Left() == Right() 中谁先被调用做出一致的决定。他们不可能都赢。

但是,Python 数据模型确实允许您在这里做您想做的事情。您可以从两侧控制此比较,但您需要正确定义 AnyOf如果您希望AnyOf从右侧控制__eq__,您必须将其定义为np.ndarray的子class。

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

不对,你这里有一个根本性的误解。左侧总是首先尝试相等比较,除非右侧是左侧类型的子class。

arr == AnyOf(2,3)

在上面的例子中,你的自定义 __eq__ 被调用,因为 numpy 数组调用它!所以 np.ndarray 获胜,它决定每个元素检查一次。它实际上可以做任何其他事情,包括根本不调用您的 AnyOf.__eq__

AnyOf(2,3) == arr

在上面的例子中,你的 class 确实在比较中得到了第一次尝试,但由于你使用 in 的方式(检查数组是否在元组中)而失败了。