对象相等性 (`==`) 的默认行为在哪里定义?

11
根据 object.__eq__()文档==的默认实现(即在 object 类中)如下:
True if x is y else NotImplemented

仍然按照文档中NotImplemented的说明,我推断出NotImplemented意味着Python运行时将尝试以另一种方式进行比较。即如果x.__eq__(y)返回NotImplemented(在==操作符的情况下),则尝试y.__eq__(x)

现在,在Python 3.9中,以下代码会打印FalseTrue

class A:
   pass
print(A() == A())
print(bool(NotImplemented))

所以我的问题是:文档在哪里提到了在__eq__上下文中NotImplemented的特殊行为? PS:我在CPython源代码中找到了答案,但我猜这必须/应该出现在文档中。

8
文档误导,实际上存在一个跟踪问题:https://github.com/python/cpython/issues/83292 - whilrun
这个答案 https://stackoverflow.com/a/57134523/7517724 提供了一个链接,其中包含GvRs解释更改的提交记录,但这并不能解释为什么它没有在文档中。 - Craig
我认为有趣的是注意到x = A(),y = A(),print(x==x),print(x==y)返回分别为TrueFalse - BeRT2me
1
@BeRT2me 这是因为 x == x 首先使用身份进行比较:https://docs.python.org/3/c-api/object.html#c.PyObject_RichCompareBool - Ashwini Chaudhary
2
@YaakovBressler 他们都已经被实例化了。我不知道你所说的“内联寄存器”的意思。为了让A() == A()中的对象存在足够长的时间以进行比较,它们必须是不同的对象。默认逻辑会使它们比较为false,因为它们不是同一个对象。 - Karl Knechtel
显示剩余2条评论
1个回答

6
根据object.__eq__()文档,==的默认实现(即在对象类中)如下所示。
不是的;那是__eq__的默认实现。作为运算符,==不能在类中实现。
Python对运算符的实现是协作的。有硬编码逻辑使用dunder方法来确定应该发生什么,并可能回退到默认值。这个逻辑在任何类之外。
你可以看到另一个例子内置的len:一个类可以从它的__len__方法返回任何它喜欢的值,你原则上可以直接调用它并得到任何类型的值。然而,这并没有正确地实现协议,当它没有得到非负整数时,len会抱怨。没有任何包含那种类型检查和值检查逻辑的类。它是外部的。
仍然遵循NotImplemented的文档,我推断NotImplemented意味着Python运行时将尝试以另一种方式进行比较。也就是说,在==运算符的情况下,如果x.__eq__(y)返回NotImplemented,则尝试y.__eq__(x)NotImplemented只是一个对象。它不是语法。它没有任何特殊行为,在Python中,简单地返回一个值不会触发特殊行为,除了该值被返回。
二元操作符的外部代码将尝试查找匹配的__op__,如果__op__不起作用,则尝试查找匹配的__rop__。此时,NotImplemented不是可接受的答案(它是专门为此目的存在的标记,因为None是可以接受的答案)。通常情况下,如果到目前为止的答案仍然是NotImplemented,则外部代码将引发NotImplementedError
作为一个特殊情况,如果对象没有提供自己的比较方法(即使用object的默认值来进行__eq____ne__),除非它们是相同的,否则它们将被比较为“不相等”。C implementation重复了身份检查(如果类明确定义__eq____ne__直接返回NotImplemented,我猜测)。这是因为考虑到给出这个结果是明智的,而让==一直失败当有一个合理的默认值时是令人讨厌的。
然而,如果没有明确的逻辑,这两个对象仍然无法进行排序,因为没有合理的默认值。(您可以比较指针值,但它们是任意的,并且与将您带到该点的Python逻辑无关;因此,以这种方式排序实际上对编写Python代码没有实用价值。)例如,如果未提供比较逻辑,则x < y将引发TypeError。(即使x is y也是如此;您可以合理地说,在这种情况下<=>=应该为真,<>应该为假,但这会使事情变得太复杂,也不是很有用。)

[观察:print(bool(NotImplemented))打印True]

嗯,是的;NotImplemented是一个对象,因此默认情况下它是真实的;它不表示数值,也不是容器,因此没有理由让它成为虚假的。

然而,这也没有告诉我们任何有用的信息。我们不关心NotImplemented的真实性,并且在Python实现中也不是以这种方式使用。它只是一个标记值。

文档在哪里提到了__eq__上下文中NotImplemented的特殊行为?

没有提到,因为正如上面所解释的那样,这不是NotImplemented的行为。

好的,但这留下了一个根本性的问题:文档在哪里解释了默认情况下==运算符的作用?

答案:因为我们正在讨论运算符,而不是方法,所以它不在dunder方法部分。它在第6节中,该节讨论表达式。具体来说,参见6.10.1. Value comparisons

默认情况下,等式比较(==!=)的行为基于对象的身份。因此,具有相同标识的实例之间的等式比较结果为相等,而具有不同标识的实例之间的等式比较结果为不等。这种默认行为的动机是希望所有对象都应该是自反的(即x is y意味着x == y)。

最后部分是我也看过的,但它并没有完全说服我,“具有不同标识的实例之间的相等比较结果为不相等”可能只意味着!=导致True。但我猜你是对的,它还应该意味着==导致False - Kelly Bundy
“(x == y) == not (x != y)”是一个单独的不变量,在本节稍后列出:“反向比较应该导致布尔否定。换句话说,以下表达式应该具有相同的结果:”。 - Karl Knechtel
这是在谈论“自定义类对比行为应该如何”这个主题,所以它讨论的是我们的类在__eq__层面上应该做什么。而不是Python在==层面上确实做了什么。 - Kelly Bundy
我猜吧,但我觉得我们现在已经开始严格挑剔了。 - Karl Knechtel
@KarlKnechtel非常感谢您详细的回答。我仍然认为文档可以更清晰明了。我们可以在文档的数据模型部分阅读到“x==y calls x.__eq__(y)”和“默认情况下,对象通过使用is实现__eq __(),在假比较的情况下返回NotImplementedTrue if x is y else NotImplemented。”。阅读这些内容,而不知道第6节也涉及==,我感到很迷惑。 - Manuel Selva
有很多地方文档可以更加清晰明了。开发团队最近开始更加关注这类事情,这也是与3.10改进的错误信息相同努力的一部分。 - Karl Knechtel

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