为啥 Python 没有“__req__”(反射相等)方法?
Posted
技术标签:
【中文标题】为啥 Python 没有“__req__”(反射相等)方法?【英文标题】:Why doesn't Python have a "__req__" (reflected equality) method?为什么 Python 没有“__req__”(反射相等)方法? 【发布时间】:2018-05-14 14:45:22 【问题描述】:我有一个小助手类:
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__
等方法。)
【问题讨论】:
"为什么 Python 没有 X?"不太可能是一个非常有成效的问题。如果您希望 Python 拥有 X,实现这一目标的有效方法是关注 the process in PEP-1 来实际提出这个想法,并从对添加到语言中的内容有发言权的人那里获得反馈。否则,“有没有办法在 Python 中获得 X 的有用效果?”实际上更有可能立即产生有用的结果(例如产生光而不是热量)。 相关:***.com/questions/3588776/… 实际上,在上面的示例中,我无法认识到对类的迫切需求。为什么不使用一个简单的函数,它返回一个列表? 请注意,如果您有一些较大的数组,则此“魔术”将比np.in1d
之类的要慢得多。
通常,__eq__
应该返回 NotImplemented
而不是 False
,除非绝对确定值不同。
【参考方案1】:
关于 __rxx__
方法的 documentation __radd__
声明:
这些函数仅在左操作数不支持 对应的操作,操作数的类型不同。
虽然默认情况下类没有__add__
或__sub__
方法,但它们有有__eq__
:
>>> class A(object):
... pass
>>> '__eq__' in dir(A)
True
这意味着__req__
永远不会被调用,除非您从其他类中显式删除__eq__
或使__eq__
返回NotImplemented
。
您可以通过np.in1d
解决您的具体问题:
>>> np.in1d(arr, [2, 3])
array([False, True, True, False, False], dtype=bool)
【讨论】:
这意味着__req__
永远不会被调用,除非您从其他类中明确删除__eq__
NotImplemented,实际上这实际上是从object.__eq__
继承的默认行为 - 它通过身份(“is”)实现相等,并在不同比较的情况下返回NotImplemented
。
感谢您指出这一点。更正了我的答案。【参考方案2】:
__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
(检查数组是否在元组中)的方式而失败。
【讨论】:
关于“__req__
在语言中不是一个好主意”以及随后的论点,我认为这并不成立,因为从同一个论点来看,像 __radd__
这样的方法也会不是一个好主意。但是,我现在确实看到,由于选择了哪个模型(Left.__eq__
或 Right.__req__
),它实际上会使得__req__
将实现与__eq__
完全相同的功能,使右手方法多余!【参考方案3】:
这是data model上的文档:
这些方法没有交换参数版本(当左参数不支持操作但 正确的论点确实如此);相反,
__lt__()
和__gt__()
是彼此的 反射,__le__()
和__ge__()
是彼此的反射,并且__eq__()
和__ne__()
是他们自己的反映。如果操作数的类型不同,并且右操作数的类型是直接的还是间接的 左操作数类型的子类,右操作数的反射方法 操作数有优先权,否则左操作数的方法有 优先。不考虑虚拟子类化。
如上面的 cmets 所述,你想要的工作,__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
但是,某些对象可能会在 __eq__
上产生 TypeError 而不是返回 NotImplemented
或 False
,这使得这对于所有类型的对象都不可靠。
在您的情况下发生的情况是,在您自己的 __eq__
方法中错误地使用了运算符 in
与数组和元组。 (感谢@wim 在这里的另一个答案中发现了这一点)。
【讨论】:
+1 这是一个很大的“如果” - 一些标准类型只会引发异常而不是返回NotImplemented
,所以你不能依赖这个。
我只是不确定它是否适用于 np.array
。但如果不是,那是 np.array
中的一个错误,而不是语言中缺少的东西。
我没说可以依赖它。以上是关于为啥 Python 没有“__req__”(反射相等)方法?的主要内容,如果未能解决你的问题,请参考以下文章