8

我有一个小助手类:

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的类的__eq__方法永远不会被调用:相反,NumPy 数组的__eq__方法被调用,它在内部(我假设)调用其__eq__所有元素的方法。

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

4

3 回答 3

8

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

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

如果我这样做,AnyOf(2,3) == arr那么我AnyOf的班级的__eq__方法永远不会被调用

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

arr == AnyOf(2,3)

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

AnyOf(2,3) == arr

在上述情况下,您的类确实在比较中获得了第一次尝试,但由于您使用的方式in(检查数组是否在元组中)而失败。

于 2017-11-30T16:25:09.440 回答
3

有关方法的文档如下:__rxx____radd__

只有当左操作数不支持相应的操作并且操作数属于不同类型时,才会调用这些函数。

虽然默认情况下类没有__add__或方法,但它们确实有:__sub____eq__

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

这意味着__req__永远不会被调用,除非您明确地__eq__从其他类中删除或 make __eq__return NotImplemented

您可以通过以下方式解决您的具体问题np.in1d

>>> np.in1d(arr, [2, 3])
array([False,  True,  True, False, False], dtype=bool)
于 2017-11-30T16:07:06.810 回答
2

这是有关数据模型的文档:

这些方法没有交换参数版本(当左参数不支持操作但右参数支持时使用);更确切地说,__lt__()__gt__()是彼此的反映,__le__()并且__ge__()是彼此的反映, __eq__()并且__ne__()是自己的反映。如果操作数的类型不同,并且右操作数的类型是左操作数类型的直接或间接子类,则右操作数的反射方法优先,否则左操作数的方法优先。不考虑虚拟子类化。

如上面的评论所述,您想要的工作,并且__eq__本质上与潜在相同:如果左侧的对象返回,则__req__在右侧调用它:==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

但是,某些对象可能会产生 TypeError on__eq__而不是返回NotImplementedor False,这使得这对于所有类型的对象都不可靠。

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

于 2017-11-30T16:14:25.913 回答