为啥 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 而不是返回 NotImplementedFalse,这使得这对于所有类型的对象都不可靠。

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

【讨论】:

+1 这是一个很大的“如果” - 一些标准类型只会引发异常而不是返回NotImplemented,所以你不能依赖这个。 我只是不确定它是否适用于 np.array。但如果不是,那是 np.array 中的一个错误,而不是语言中缺少的东西。 我没说可以依赖它。

以上是关于为啥 Python 没有“__req__”(反射相等)方法?的主要内容,如果未能解决你的问题,请参考以下文章

python对象反射和函数反射

Python 之 类的反射

为啥用串口调试助手和pic单片机相连发的指令都没有反应呢。

反射基本知识

为啥对 HttpResponseCache 使用反射?

SpringMVC为啥是基于反射机制