当d1 == d2不等同于d1.__eq__(d2)时是什么情况?

4

根据文档(在Python 3.8中):

默认情况下,object通过使用is实现__eq __(),在假比较的情况下返回NotImplementedTrue if x is y else NotImplemented

并且:

运算符符号和方法名称之间的对应关系如下:[...] x==y调用x.__eq__(y)

所以我期望:

  1. ==等同于__eq__(),并且
  2. 一个没有明确定义__eq__的自定义类,在使用==比较两个不同实例的类时返回NotImplemented。 然而,在以下情况下, == 比较返回 False ,而__eq__()返回NotImplemented
class Dummy():
    def __init__(self, a):
        self.a = a

d1 = Dummy(3)
d2 = Dummy(3)

d1 == d2 # False
d1.__eq__(d2) # NotImplemented

为什么?

Duplicate? - Kelly Bundy
3个回答

7

原因是如果操作的一方无法(或不愿)提供答案,则允许另一方发表意见以处理此比较。一个常见的例子是浮点数/整数比较:

>>> 1 == 1.0
True
>>> (1).__eq__(1.0)
NotImplemented
>>> (1.0).__eq__(1)
True

intfloat 中,两者都不是彼此的子类,而且一个 int 不能说明它是否等于一些 float。相等比较由 float 类型处理。

如果您有左右两边的不同类型,并在钩子中添加一些调试输出,则更容易理解:

class Left:
    def __eq__(self, other):
        print("trying Left.__eq__")
        return NotImplemented

class Right:
    def __eq__(self, other):
        print("trying Right.__eq__")
        return True

演示:

>>> d1 = Left()
>>> d2 = Right()
>>> d1.__eq__(d2)
trying Left.__eq__
NotImplemented
>>> d2.__eq__(d1)
trying Right.__eq__
True
>>> d1 == d2  # Left.__eq__ "opts out" of the comparison, Python asks other side
trying Left.__eq__
trying Right.__eq__
True
>>> d2 == d1  # Right.__eq__ returns a result first, Left.__eq__ isn't considered
trying Right.__eq__
True

左侧的type(d1).__eq__通过返回NotImplemented退出,这使得右侧有“第二次机会”来处理操作。如果左侧返回False而不是返回NotImplemented,Python将根本不尝试右侧,结果将为d1 == d2False。如果两侧都返回NotImplemented,就像您的Dummy示例一样,则认为对象不相等,除非它们是相同的(即同一实例)。


好吧,不一定是两边,但如果左边拒绝或无法提供答案,右边就有机会。 如果Left.__eq__返回一个值,则不会咨询Right.__eq__ - chepner
1
@chepner 这并不是很简单。如果Right是Left的子类,那么右侧将首先被尝试。 - wim
但是在这种情况下,如果Right.__eq__返回一个值,那么将不会调用Left.__eq__。你最初的措辞暗示了两者都可以被调用,并且实际结果可能是两者的某种组合。(是的,我在这里非常地追求精确。我已经给你点赞了 :)) - chepner
@chepner 嗯?对,Right也可以返回NotImplemented,然后Left将被视为第二个选项。 - wim
当然,但在您的示例中并没有这样做。我的观点是*两者都不被调用,并且每个返回值用于确定d1 == d2的最终结果。无论哪个先被调用,都会通过返回一个除NotImplemented之外的值来防止另一个被调用。 - chepner
显示剩余2条评论

4
这里的文档不是很清晰,明显需要改进。
当 Python “内部”方法返回 NotImplemented 时,通常意味着“找到其他方法执行此操作”。当 Python 找不到“其他方法”时,具体如何处理取决于操作,文档不够详细。例如,如果 x.__lt__(y) 返回 NotImplemented,Python 可能会尝试调用 y.__gt__(x)。
对于 == 运算符,如果 x.__eq__(y) 和 y.__eq__(x) 都返回 NotImplemented,Python 返回 False。这一点应该有更好的文档说明。

https://docs.python.org/3/reference/datamodel.html#object.__lt__ - Mad Physicist
如果我理解正确,那么这句话可以改为“......都返回NotImplementedx不是y”,对吗? - wjandrea
@chepner,是的,有一些奇怪的边缘情况 - 问问你的nan - wim
我不认为float.__eq__是一个好的定义。但IEEE 754尽力解决了在不太理想的数学条件下浮点运算的实际问题。 :) - chepner
@wim nan.__eq__(nan)不会返回NotImplemented,因此它不会进入第二步x is not y - iBug
显示剩余2条评论

2

== 调用右操作数的 .__eq__() 如果左操作数的 .__eq__() 返回 NotImplemented如果两边都返回 NotImplemented== 将返回 false。

您可以通过更改类来查看此行为:

class Dummy():
    def __eq__(self, o):
        print(f'{id(self)} eq')
        return NotImplemented

d1 = Dummy()
d2 = Dummy()
print(d1 == d2)
# 2180936917136 eq
# 2180936917200 eq
# False

这是运算符的常见行为,Python会测试左操作数是否有实现,如果没有,Python将调用右操作数相同或反射的(例如,如果未实现x.__lt__(y),则调用y.__gt__(x))运算符。

class Dummy:
    def __eq__(self, o):
        print(f"{id(self)} eq")
        return NotImplemented

    def __lt__(self, o):
        """reflexion of gt"""
        print(f"{id(self)} lt")
        return NotImplemented

    def __gt__(self, o):
        """reflexion of lt"""
        print(f"{id(self)} gt")
        return False

    def __le__(self, o):
        """reflexion of ge"""
        print(f"{id(self)} le")
        return NotImplemented

    def __ge__(self, o):
        """reflexion of le"""
        print(f"{id(self)} ge")
        return False



d1 = Dummy()
d2 = Dummy()

print(d1 == d2)
# 2480053379984 eq
# 2480053380688 eq
# False

print(d1 < d2)
# 2480053379984 lt
# 2480053380688 gt
# False

print(d1 <= d2)
# 2480053379984 le
# 2480053380688 ge
# False

! 左右异常:

如果操作数的类型不同,并且右操作数的类型是左操作数类型的直接或间接子类,则右操作数的反射方法具有优先权,否则左操作数的方法具有优先权。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接