Python,我是否应该基于__eq__实现__ne __()运算符?


98

我有一个要覆盖__eq__()运算符的类。我也应该重写__ne__()运算符似乎很有意义,但是__ne__基于__eq__这样的实现是否有意义?

class A:
    def __eq__(self, other):
        return self.value == other.value

    def __ne__(self, other):
        return not self.__eq__(other)

还是Python缺少使用这些运算符的方式而导致的一个好主意?

Answers:


57

是的,那很好。实际上,文档敦促您在定义__ne__时定义__eq__

比较运算符之间没有隐含的关系。的真相x==y并不意味着那x!=y 是错误的。因此,在定义时 __eq__(),还应该定义一个,__ne__()以便操作符能够按预期运行。

在很多情况下(例如此情况),它__eq__与否的结果一样简单,但并不总是如此。


12
是正确的答案(在这里,@ aaron-hall)。你所引用的文件并没有鼓励你实现__ne__使用__eq__,只是你实现它。
Guyarad

2
@guyarad:实际上,由于没有适当委派,Aaron的答案仍然略有错误;(假定操作数不知道如何比较另一操作数)不是NotImplemented从一侧返回作为委托另一侧的提示__ne__not self == other而是从另一侧__eq__隐式委托给__eq__它,然后将其反转。对于奇怪的类型,例如SQLAlchemy ORM的字段,这会引起问题
ShadowRanger

1
ShadowRanger的批评仅适用于非常病理性的病例(IMHO),下面我的答复中已完全解决。
亚伦·霍尔

1
较新的文档(至少适用于3.7,甚至可能更早)__ne__将自动委托给,__eq__并且文档中不再存在此答案中的引用。最重要的是,仅实现__eq__并让其__ne__委托完全是pythonic 。
bluesummers

132

Python,我应该实现__ne__()基于的运算符__eq__吗?

简短的回答:不要实现它,但是如果必须的话,请使用==,而不是__eq__

在Python 3中,默认情况下!=是否定==,因此您甚至不需要编写__ne__,并且文档不再赘述。

一般而言,对于仅Python 3的代码,除非您需要使父实现(例如,内置对象)蒙上阴影,否则不要编写任何代码。

也就是说,请记住Raymond Hettinger的评论

只有在超类中尚未定义时,该__ne__方法__eq__才 自动从该方法__ne__开始。因此,如果您要从内置继承,则最好同时覆盖两者。

如果您需要代码在Python 2中运行,请遵循针对Python 2的建议,它将在Python 3中正常运行。

在Python 2中,Python本身不会自动根据另一个执行任何操作-因此,您应__ne__使用==而不是来定义__eq__。例如

class A(object):
    def __eq__(self, other):
        return self.value == other.value

    def __ne__(self, other):
        return not self == other # NOT `return not self.__eq__(other)`

看到证明

  • __ne__()基于__eq__和实现操作符
  • 根本没有__ne__在Python 2中实现

在下面的演示中提供了错误的行为。

长答案

Python 2 的文档说:

比较运算符之间没有隐含的关系。的真相x==y并不意味着那x!=y是错误的。因此,在定义时__eq__(),还应该定义一个,__ne__()以便操作符能够按预期运行。

因此,这意味着,如果我们__ne__根据的倒数进行定义__eq__,我们可以获得一致的行为。

文档的这一部分已针对Python 3更新

默认情况下,除非为,否则将结果__ne__()委托__eq__()并反转NotImplemented

“新功能”部分中,我们看到此行为已更改:

  • !=现在返回与的相反==,除非==返回NotImplemented

为了实现__ne__,我们更喜欢使用==运算符,而不是__eq__直接使用方法,以便如果self.__eq__(other)子类返回NotImplemented了所检查类型,Python将适当地other.__eq__(self) 从文档中进行检查:

NotImplemented对象

此类型具有单个值。有一个具有此值的对象。通过内置名称访问该对象 NotImplemented。如果数字方法和丰富比较方法未实现所提供操作数的操作,则可能返回此值。(然后,解释程序将根据操作员尝试执行反射操作或其他回退。)其真实值是true。

当给定一个丰富比较运算符,如果他们不相同的类型,Python中检查是否other是一个子类型,并且如果它具有定义的操作者,它使用other第一的方法(逆为<<=>=>)。如果NotImplemented返回,使用相反的方法。(它不是检查相同的方法两次。)使用==操作员允许这种逻辑发生。


期望

从语义上讲,您应该__ne__按照是否相等的检查来实现,因为类的用户将期望以下函数对A的所有实例都等效:

def negation_of_equals(inst1, inst2):
    """always should return same as not_equals(inst1, inst2)"""
    return not inst1 == inst2

def not_equals(inst1, inst2):
    """always should return same as negation_of_equals(inst1, inst2)"""
    return inst1 != inst2

也就是说,以上两个函数应始终返回相同的结果。但这取决于程序员。

演示__ne__基于以下内容的意外行为__eq__

首先设置:

class BaseEquatable(object):
    def __init__(self, x):
        self.x = x
    def __eq__(self, other):
        return isinstance(other, BaseEquatable) and self.x == other.x

class ComparableWrong(BaseEquatable):
    def __ne__(self, other):
        return not self.__eq__(other)

class ComparableRight(BaseEquatable):
    def __ne__(self, other):
        return not self == other

class EqMixin(object):
    def __eq__(self, other):
        """override Base __eq__ & bounce to other for __eq__, e.g. 
        if issubclass(type(self), type(other)): # True in this example
        """
        return NotImplemented

class ChildComparableWrong(EqMixin, ComparableWrong):
    """__ne__ the wrong way (__eq__ directly)"""

class ChildComparableRight(EqMixin, ComparableRight):
    """__ne__ the right way (uses ==)"""

class ChildComparablePy3(EqMixin, BaseEquatable):
    """No __ne__, only right in Python 3."""

实例化非等效实例:

right1, right2 = ComparableRight(1), ChildComparableRight(2)
wrong1, wrong2 = ComparableWrong(1), ChildComparableWrong(2)
right_py3_1, right_py3_2 = BaseEquatable(1), ChildComparablePy3(2)

预期行为:

(请注意:虽然以下各项的第二个断言都是等效的,因此在逻辑上与其之前的一个断言是多余的,但我将它们包括在内以证明当一个是另一个的子类时顺序并不重要。

这些实例通过以下方式__ne__实现==

assert not right1 == right2
assert not right2 == right1
assert right1 != right2
assert right2 != right1

这些实例(在Python 3下测试)也可以正常运行:

assert not right_py3_1 == right_py3_2
assert not right_py3_2 == right_py3_1
assert right_py3_1 != right_py3_2
assert right_py3_2 != right_py3_1

并回想起这些已__ne__通过__eq__- 实现,尽管这是预期的行为,但实现不正确:

assert not wrong1 == wrong2         # These are contradicted by the
assert not wrong2 == wrong1         # below unexpected behavior!

意外行为:

请注意,此比较与上述(not wrong1 == wrong2)比较相矛盾。

>>> assert wrong1 != wrong2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError

和,

>>> assert wrong2 != wrong1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError

不要__ne__在Python 2中跳过

有关不应该跳过__ne__在Python 2中实现的证据,请参见以下等效对象:

>>> right_py3_1, right_py3_1child = BaseEquatable(1), ChildComparablePy3(1)
>>> right_py3_1 != right_py3_1child # as evaluated in Python 2!
True

以上结果应该是False

Python 3源码

的默认CPython实现__ne__typeobject.cobject_richcompare

case Py_NE:
    /* By default, __ne__() delegates to __eq__() and inverts the result,
       unless the latter returns NotImplemented. */
    if (Py_TYPE(self)->tp_richcompare == NULL) {
        res = Py_NotImplemented;
        Py_INCREF(res);
        break;
    }
    res = (*Py_TYPE(self)->tp_richcompare)(self, other, Py_EQ);
    if (res != NULL && res != Py_NotImplemented) {
        int ok = PyObject_IsTrue(res);
        Py_DECREF(res);
        if (ok < 0)
            res = NULL;
        else {
            if (ok)
                res = Py_False;
            else
                res = Py_True;
            Py_INCREF(res);
        }
    }
    break;

但是默认__ne__使用__eq__

__ne__C 3级别使用Python 3的默认实现细节,__eq__因为更高级别==PyObject_RichCompare)的效率较低-因此,它也必须处理NotImplemented

如果__eq__正确实现,则对的取反==也是正确的-并且它使我们能够避免在中使用低级实现细节__ne__

使用==可以使我们将底层逻辑保持在一个地方,并避免NotImplemented在中寻址__ne__

一个人可能错误地认为==可能返回NotImplemented

实际上,它使用与的默认实现相同的逻辑__eq__,以检查身份(请参阅下面的do_richcompare和我们的证据)

class Foo:
    def __ne__(self, other):
        return NotImplemented
    __eq__ = __ne__

f = Foo()
f2 = Foo()

和比较:

>>> f == f
True
>>> f != f
False
>>> f2 == f
False
>>> f2 != f
True

性能

不要相信我,让我们看看更有效的方法:

class CLevel:
    "Use default logic programmed in C"

class HighLevelPython:
    def __ne__(self, other):
        return not self == other

class LowLevelPython:
    def __ne__(self, other):
        equal = self.__eq__(other)
        if equal is NotImplemented:
            return NotImplemented
        return not equal

def c_level():
    cl = CLevel()
    return lambda: cl != cl

def high_level_python():
    hlp = HighLevelPython()
    return lambda: hlp != hlp

def low_level_python():
    llp = LowLevelPython()
    return lambda: llp != llp

我认为这些表现数字说明了一切:

>>> import timeit
>>> min(timeit.repeat(c_level()))
0.09377292497083545
>>> min(timeit.repeat(high_level_python()))
0.2654011140111834
>>> min(timeit.repeat(low_level_python()))
0.3378178110579029

当您认为这样low_level_python做是在Python中执行本来可以在C级别处理的逻辑时,这才有意义。

对一些批评家的回应

另一个回答者写道:

亚伦·霍尔(Aaron Hall)not self == other__ne__方法的实现是不正确的,因为它永远不会返回NotImplementednot NotImplementedis False),因此__ne__具有优先级的方法永远不会退回到__ne__没有优先级的方法。

__ne__从来没有回报NotImplemented并不能使它不正确。相反,我们NotImplemented通过与的相等性检查来处理优先级==。假设==正确实施,我们就完成了。

not self == other曾经是该方法的默认Python 3实现,__ne__但它是一个错误,正如ShadowRanger所注意到的,它在2015年1月的Python 3.4中已得到纠正(请参阅问题#21408)。

好吧,让我们解释一下。

如前所述,Python 3默认情况下__ne__通过首先检查是否self.__eq__(other)返回NotImplemented(单例)来处理-应该使用with进行检查,is如果返回则返回,否则应返回相反的值。这是作为类mixin编写的逻辑:

class CStyle__ne__:
    """Mixin that provides __ne__ functionality equivalent to 
    the builtin functionality
    """
    def __ne__(self, other):
        equal = self.__eq__(other)
        if equal is NotImplemented:
            return NotImplemented
        return not equal

为了确保C级Python API的正确性,这是必需的,它是在Python 3中引入的,

多余的。所有相关的__ne__方法被拆除,其中包括实施自己的支票,以及那些委托给那些__eq__直接或通过==-和==是这样做的最常见的方式。

对称重要吗?

我们持批评态度提供了一个病态的例子,使办案NotImplemented__ne__,重视高于一切的对称性。让我们用一个清晰​​的例子来说明这个论点:

class B:
    """
    this class has no __eq__ implementation, but asserts 
    any instance is not equal to any other object
    """
    def __ne__(self, other):
        return True

class A:
    "This class asserts instances are equivalent to all other objects"
    def __eq__(self, other):
        return True

>>> A() == B(), B() == A(), A() != B(), B() != A()
(True, True, False, True)

因此,通过这种逻辑,为了保持对称性__ne__,无论Python版本如何,我们都需要编写复杂的。

class B:
    def __ne__(self, other):
        return True

class A:
    def __eq__(self, other):
        return True
    def __ne__(self, other):
        result = other.__eq__(self)
        if result is NotImplemented:
            return NotImplemented
        return not result

>>> A() == B(), B() == A(), A() != B(), B() != A()
(True, True, True, True)

显然,我们不应该考虑这些实例是否相等。

我建议对称性不如假定合理的代码并遵循文档的建议重要。

但是,如果A明智地实现__eq__,那么我们仍然可以按照我的指示进行操作,并且仍然具有对称性:

class B:
    def __ne__(self, other):
        return True

class A:
    def __eq__(self, other):
        return False         # <- this boolean changed... 

>>> A() == B(), B() == A(), A() != B(), B() != A()
(False, False, True, True)

结论

对于Python 2兼容代码,请使用==实现__ne__。更重要的是:

  • 正确
  • 简单
  • 表演者

仅在Python 3中,在C级别使用低级取反-它甚至更加简单和高效(尽管程序员负责确定它是正确的)。

再次,做高层次的Python编写底层逻辑。


3
优秀的例子!令人惊讶的部分是,操作数的顺序根本不重要,这与某些带有“右侧”反射的魔术方法不同。要再次重申我错过的部分(这花了我很多时间):首先尝试使用子类的丰富比较方法,而不管代码是在运算符的左侧是超类还是子类。这就是为什么您a1 != c2返回的False---它没有运行a1.__ne__,但是c2.__ne__,却否定了mixin的 __eq__方法。既然NotImplemented是真实的,not NotImplementedFalse
凯文·大通

2
您最近的更新确实成功地展示了的性能优势not (self == other),但是没有人认为它的速度不快(无论如何,它比Py2上的任何其他选项都要快)。问题是在某些情况下是错误的。Python本身曾经这样做not (self == other),但由于存在任意子类而错误,因此发生了变化。最快得出错误的答案仍然是错误的
ShadowRanger

1
具体的例子确实是不重要的。问题在于,在您的实现中,您的__ne__代表的行为__eq__(必要时可以是双方的行为),但是即使双方都“放弃” ,也绝不能退回到__ne__另一方的行为__eq__。正确的__ne__委托给自己的 委托__eq__,但是如果返回NotImplemented,它将回退到另一方__ne__,而不是反转另一方__eq__(因为另一方可能未明确选择委派给__eq__,并且您不应该为此做出决定)。
ShadowRanger

1
@AaronHall:在今天重新检查这一点时,我认为您的实现通常不会对子类造成问题(要弄乱它会非常费事,而假定对父类有充分了解的子类应该能够避免这种情况) )。但是我在回答中只举了一个不复杂的例子。非病理性情况是SQLAlchemy的ORM,其中既不__eq__也不__ne__返回Trueor False,也不返回,而是一个代理对象(碰巧是“真实的”)。错误地实现__ne__意味着顺序对于比较而言很重要(您只能以一种顺序获得代理)。
ShadowRanger

1
需要明确的是,在99%(或99.999%)的情况下,您的解决方案很好,并且(显然)更快。但是,因为你没有在那里的情况下,控制不是很好,作为一个库作家,其代码可以被别人使用(阅读:什么,但简单的一次性的脚本和模块仅供个人使用),你必须使用正确的实现方式来遵守操作员重载的一般约定,并使用可能遇到的任何其他代码。幸运的是,在Py3上,这无关紧要,因为您可以__ne__完全省略。从现在开始一年后,Py2将会死亡,我们将忽略这一点。:-)
ShadowRanger

10

仅作记录,一个规范正确且可交叉的Py2 / Py3便携式计算机__ne__看起来像:

import sys

class ...:
    ...
    def __eq__(self, other):
        ...

    if sys.version_info[0] == 2:
        def __ne__(self, other):
            equal = self.__eq__(other)
            return equal if equal is NotImplemented else not equal

这适用于__eq__您可能定义的任何对象:

  • 不像not (self == other),不涉及一些比较烦人/复杂的情况下干扰,其中所涉及的类别之一,并不意味着结果__ne__是一样的结果not__eq__(如SQLAlchemy的的ORM,其中两个__eq____ne__返回特殊的代理对象,没有TrueFalse,并尝试not的结果__eq__将返回False,而不是正确的代理对象)。
  • 不像not self.__eq__(other),这个正确委托给__ne__其他实例的时候self.__eq__回报NotImplementednot self.__eq__(other)将额外错误的,因为NotImplemented是truthy,所以当__eq__不知道如何进行比较,__ne__将返回False,这意味着这两个对象是相等的,而实际上只询问的对象不知道,这意味着不相等的默认值)

如果您__eq__不使用NotImplemented退货,则可以正常工作(无意义的开销),如果NotImplemented有时使用退货,则可以正确处理它。而且,Python版本检查意味着如果该类import在Python 3中为-ed ,则将__ne__保持未定义状态,从而可以接替Python的本机高效后备__ne__实现(上述版本的C版本)


为什么需要这个

Python重载规则

为什么要这样做而不是其他解决方案的解释有些不可思议。Python有一些关于重载运算符的通用规则,尤其是比较运算符:

  1. (适用于所有运算符)运行时LHS OP RHS,请尝试LHS.__op__(RHS),如果返回NotImplemented,请尝试RHS.__rop__(LHS)。例外:如果RHS是的类的子LHS类,则RHS.__rop__(LHS) 进行测试。在比较操作符的情况下,__eq____ne__是自己的“ROP” S(所以测试顺序__ne__LHS.__ne__(RHS),那么RHS.__ne__(LHS),逆转如果RHS是的一个子类LHS的类)
  2. 除了“交换”运算符的概念外,运算符之间没有隐含的关系。即使是同一类的实例,LHS.__eq__(RHS)返回True也并不意味着LHS.__ne__(RHS)返回False(实际上,甚至不需要运算符返回布尔值; SQLAlchemy之类的ORM故意不这样做,从而允许更具表达性的查询语法)。从Python 3开始,默认__ne__实现的行为方式是这样的,但是它不是契约性的。您可以__ne__采用与严格相反的方式进行覆盖__eq__

这如何适用于比较器过载

因此,当您使运算符重载时,您有两个工作:

  1. 如果您知道如何自己执行操作,请使用您自己的比较知识来进行操作(绝对不要将其隐式或显式委派给操作的另一侧;这样做可能会导致错误和/或无限递归,取决于您的操作方式)
  2. 如果您知道如何自己实现该操作,请始终返回NotImplemented,以便Python可以委派给另一个操作数的实现。

问题所在 not self.__eq__(other)

def __ne__(self, other):
    return not self.__eq__(other)

从不委托给另一方(如果__eq__正确返回,则是不正确的NotImplemented)。当self.__eq__(other)收益NotImplemented(这是“truthy”),你不返回False,这样A() != something_A_knows_nothing_about的回报False,当它应该检查是否something_A_knows_nothing_about知道如何比较的情况下A,如果没有,就应该已经返回True(如果双方都不知道如何自相比之下,它们被认为是不相等的)。如果A.__eq__执行不正确(返回False而不是NotImplemented当它无法识别另一侧时),那么从A的角度来看,这是“正确的” ,返回True(因为A认为不相等,所以不相等),但是可能错误的something_A_knows_nothing_about的观点,因为它从来没有问过something_A_knows_nothing_aboutA() != something_A_knows_nothing_about结束了True,但something_A_knows_nothing_about != A()可能False返回,或其他任何返回值。

问题所在 not self == other

def __ne__(self, other):
    return not self == other

更微妙。对于99%的类,这将是正确的,包括所有__ne__与的逻辑取反的类__eq__。但是not self == other打破了上述两个规则,这意味着对于__ne__ 不是逻辑逆的类__eq__,结果再次是非对称的,因为永远不会询问其中一个操作数是否可以实现__ne__,即使另一个操作数也可以实现操作数不能。最简单的示例是一个weirdo类,该类False将为所有比较返回,因此A() == Incomparable()A() != Incomparable()两者都返回False。使用正确的实现A.__ne__(一个NotImplemented不知道如何进行比较时返回的关系),关系是对称的。A() != Incomparable()Incomparable() != A()同意结果(因为在前一种情况下,A.__ne__return NotImplemented,然后Incomparable.__ne__return False,而在后一种情况下,直接Incomparable.__ne__返回False)。但是,当A.__ne__实现为时return not self == otherA() != Incomparable()返回True(因为A.__eq__返回,而不是NotImplemented,然后Incomparable.__eq__返回False,并将其A.__ne__反转为True),而Incomparable() != A()返回False.

您可以在此处查看此操作的示例。

显然,一类总是返回False两个__eq____ne__是有点怪。但正如前面所提到的,__eq__并且__ne__甚至不需要返回True/ False; SQLAlchemy ORM具有带有比较器的类,这些类返回用于构建查询的特殊代理对象,而不是True/ 根本不返回False(如果在布尔上下文中进行评估,它们是“真实的”,但永远不应在这样的上下文中对其进行评估)。

由于无法__ne__正确地重载,您破坏该类,如代码所示:

 results = session.query(MyTable).filter(MyTable.fieldname != MyClassWithBadNE())

将起作用(假设SQLAlchemy完全知道如何插入MyClassWithBadNESQL字符串;这可以使用类型适配器来完成,而MyClassWithBadNE无需完全配合),将期望的代理对象传递给filter,而:

 results = session.query(MyTable).filter(MyClassWithBadNE() != MyTable.fieldname)

最终将传递filter一个纯文本False,因为它self == other返回一个代理对象,并且not self == other将真实的代理对象转换为False。希望filter在处理类似的无效参数时引发异常False。尽管我敢肯定,很多人会认为MyTable.fieldname 应该在比较的左手边保持一致,但事实是,在一般情况下,没有程序上的理由来强制执行此操作,并且正确的泛型__ne__将以两种方式return not self == other起作用,而仅能起作用在一种安排中。


1
唯一正确,完整和诚实的答案(对不起,@ AaronHall)。这应该是公认的答案。
Maggyero

4

简短答案:是(但请阅读文档以正确完成操作)

ShadowRanger的__ne__方法实现是正确的(并且恰好是__ne__自Python 3.4以来该方法的默认实现):

def __ne__(self, other):
    result = self.__eq__(other)

    if result is not NotImplemented:
        return not result

    return NotImplemented

为什么?因为它保留了重要的数学属性,所以算符的对称性!=。此运算符是二进制的,因此其结果应取决于两个操作数的动态类型,而不仅仅是一个。这是通过针对允许多种调度的编程语言(例如Julia)的双调度实现的。在只允许单调度的Python中,通过在不支持其他操作数类型的实现方法中返回值,对数值方法丰富的比较方法模拟了双调度。然后,解释器将尝试另一个操作数的反射方法。NotImplemented

亚伦·霍尔(Aaron Hall)not self == other__ne__方法的实现是不正确的,因为它消除了!=操作员的对称性。实际上,它永远不会返回NotImplementednot NotImplementedis False),因此__ne__优先级较高的方法永远不会退回到__ne__优先级较低的方法。not self == other曾经是该方法的默认Python 3实现,__ne__但正如ShadowRanger所注意到的那样,它是一个错误,已在2015年1月的Python 3.4中得到纠正(请参见问题#21408)。

比较运算符的实现

Python 3 的Python语言参考在其第三章数据模型中指出:

object.__lt__(self, other)
object.__le__(self, other)
object.__eq__(self, other)
object.__ne__(self, other)
object.__gt__(self, other)
object.__ge__(self, other)

这些就是所谓的“丰富比较”方法。运算符和方法名称之间的对应关系如下:x<y调用 x.__lt__(y)x<=y调用x.__le__(y)x==y调用x.__eq__(y)x!=y调用x.__ne__(y)x>y调用x.__gt__(y)x>=y 调用x.__ge__(y)

如果富比较方法NotImplemented未实现给定参数对的操作,则可能返回单例。

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

将其转换为Python代码即可(使用operator_eqfor ==operator_nefor !=operator_ltfor <operator_gtfor >operator_lefor <=operator_gefor >=):

def operator_eq(left, right):
    if type(left) != type(right) and isinstance(right, type(left)):
        result = right.__eq__(left)

        if result is NotImplemented:
            result = left.__eq__(right)
    else:
        result = left.__eq__(right)

        if result is NotImplemented:
            result = right.__eq__(left)

    if result is NotImplemented:
        result = left is right

    return result


def operator_ne(left, right):
    if type(left) != type(right) and isinstance(right, type(left)):
        result = right.__ne__(left)

        if result is NotImplemented:
            result = left.__ne__(right)
    else:
        result = left.__ne__(right)

        if result is NotImplemented:
            result = right.__ne__(left)

    if result is NotImplemented:
        result = left is not right

    return result


def operator_lt(left, right):
    if type(left) != type(right) and isinstance(right, type(left)):
        result = right.__gt__(left)

        if result is NotImplemented:
            result = left.__lt__(right)
    else:
        result = left.__lt__(right)

        if result is NotImplemented:
            result = right.__gt__(left)

    if result is NotImplemented:
        raise TypeError(f"'<' not supported between instances of '{type(left).__name__}' and '{type(right).__name__}'")

    return result


def operator_gt(left, right):
    if type(left) != type(right) and isinstance(right, type(left)):
        result = right.__lt__(left)

        if result is NotImplemented:
            result = left.__gt__(right)
    else:
        result = left.__gt__(right)

        if result is NotImplemented:
            result = right.__lt__(left)

    if result is NotImplemented:
        raise TypeError(f"'>' not supported between instances of '{type(left).__name__}' and '{type(right).__name__}'")

    return result


def operator_le(left, right):
    if type(left) != type(right) and isinstance(right, type(left)):
        result = right.__ge__(left)

        if result is NotImplemented:
            result = left.__le__(right)
    else:
        result = left.__le__(right)

        if result is NotImplemented:
            result = right.__ge__(left)

    if result is NotImplemented:
        raise TypeError(f"'<=' not supported between instances of '{type(left).__name__}' and '{type(right).__name__}'")

    return result


def operator_ge(left, right):
    if type(left) != type(right) and isinstance(right, type(left)):
        result = right.__le__(left)

        if result is NotImplemented:
            result = left.__ge__(right)
    else:
        result = left.__ge__(right)

        if result is NotImplemented:
            result = right.__le__(left)

    if result is NotImplemented:
        raise TypeError(f"'>=' not supported between instances of '{type(left).__name__}' and '{type(right).__name__}'")

    return result

比较方法的默认实现

该文档添加:

默认情况下,除非为,否则将结果__ne__()委托__eq__()并反转NotImplemented。比较运算符之间没有其他隐含关系,例如,的真相(x<y or x==y)并不意味着x<=y

的比较方法的缺省的实现(__eq____ne____lt____gt____le____ge__)因此可以由下式给出:

def __eq__(self, other):
    return NotImplemented

def __ne__(self, other):
    result = self.__eq__(other)

    if result is not NotImplemented:
        return not result

    return NotImplemented

def __lt__(self, other):
    return NotImplemented

def __gt__(self, other):
    return NotImplemented

def __le__(self, other):
    return NotImplemented

def __ge__(self, other):
    return NotImplemented

因此,这是该__ne__方法的正确实现。而且它并不总是返回的逆__eq__方法,因为当__eq__方法返回NotImplemented,它的倒数not NotImplementedFalse(因为bool(NotImplemented)True),而不是所期望的NotImplemented

错误的实现 __ne__

正如上文的亚伦·霍尔(Aaron Hall)所展示的,not self.__eq__(other)__ne__方法不是默认的实现。但是也不是not self == other下面通过not self == other在两种情况下将默认实现的行为与实现的行为进行比较来演示后者:

  • __eq__方法返回NotImplemented;
  • __eq__方法返回的值不同于NotImplemented

默认实现

让我们看看当该A.__ne__方法使用默认实现并且该A.__eq__方法返回时会发生什么NotImplemented

class A:
    pass


class B:

    def __ne__(self, other):
        return "B.__ne__"


assert (A() != B()) == "B.__ne__"
  1. !=来电A.__ne__
  2. A.__ne__来电A.__eq__
  3. A.__eq__返回NotImplemented
  4. !=来电B.__ne__
  5. B.__ne__返回"B.__ne__"

这表明当A.__eq__方法返回时NotImplemented,该A.__ne__方法将退回到该B.__ne__方法上。

现在,让我们看看当该A.__ne__方法使用默认实现并且该A.__eq__方法返回的值不同于时会发生什么NotImplemented

class A:

    def __eq__(self, other):
        return True


class B:

    def __ne__(self, other):
        return "B.__ne__"


assert (A() != B()) is False
  1. !=来电A.__ne__
  2. A.__ne__来电A.__eq__
  3. A.__eq__返回True
  4. !=返回not True,即False

这表明在这种情况下,该A.__ne__方法返回该方法的逆函数A.__eq__。因此,该__ne__方法的行为类似于文档中所宣传的那样。

A.__ne__用上面给出的正确实现覆盖方法的默认实现会产生相同的结果。

not self == other 实施

让我们来看看重写的默认实现时,会发生什么A.__ne__与方法not self == other的实现和A.__eq__方法返回NotImplemented

class A:

    def __ne__(self, other):
        return not self == other


class B:

    def __ne__(self, other):
        return "B.__ne__"


assert (A() != B()) is True
  1. !=来电A.__ne__
  2. A.__ne__来电==
  3. ==来电A.__eq__
  4. A.__eq__返回NotImplemented
  5. ==来电B.__eq__
  6. B.__eq__返回NotImplemented
  7. ==返回A() is B(),即False
  8. A.__ne__返回not False,即True

方法的默认实现__ne__返回"B.__ne__",而不是True

现在让我们看看重写的默认实现时,会发生什么A.__ne__与方法not self == other的实现和A.__eq__方法返回从值不同NotImplemented

class A:

    def __eq__(self, other):
        return True

    def __ne__(self, other):
        return not self == other


class B:

    def __ne__(self, other):
        return "B.__ne__"


assert (A() != B()) is False
  1. !=来电A.__ne__
  2. A.__ne__来电==
  3. ==来电A.__eq__
  4. A.__eq__返回True
  5. A.__ne__返回not True,即False

在这种情况下,__ne__也会返回该方法的默认实现False

由于此实现无法__ne____eq__方法返回时复制该方法的默认实现的行为NotImplemented,因此是不正确的。


对于最后一个示例:“由于该实现__ne____eq__方法返回NotImplemented 时无法复制该方法的默认实现的行为,因此是不正确的。” - A定义无条件平等。因此,A() == B()。因此A() != B() 应该是False,它。给出的示例是病理性的(即,__ne__不应返回字符串,并且__eq__不应依赖__ne__-而是__ne__应依赖__eq__,这是Python 3中的默认期望)。除非您可以改变主意,否则我在此答案上仍为-1。
亚伦·霍尔

@AaronHall从Python语言参考:“丰富的比较方法可能返回单NotImplemented,如果它不实施一对给定参数的操作按照惯例,。FalseTrue一个成功的比较被返回。然而,这些方法可以返回任何值,因此,如果在布尔上下文中使用比较运算符(例如,在if语句的条件下),Python将调用bool()该值以确定结果是true还是false。”
玛吉耶罗

@AaronHall您的实现会__ne__杀死一个重要的数学属性,即操作员的对称性!=。该运算符是二进制的,因此其结果应取决于两个操作数的动态类型,而不仅取决于一个。这是通过双重调度在编程语言中正确实现的,而该语言是允许多次调度的语言。在仅允许单调度的Python中,通过返回NotImplemented值来模拟双调度。
Maggyero

最后一个示例有两个类,,B它们在所有检查中都返回真实字符串__ne__,并在所有检查A中返回。这是一个病理矛盾。在这样的矛盾下,最好提出一个例外。没有知识,没有义务要尊重的实施为对称的目的。那时示例中的工具与我无关。请找到一个实用的,非病理性的案例来说明您的观点。我已更新我的答案以解决您的问题。True__eq__BAB__ne__A__ne__
亚伦·霍尔

@AaronHall有关更现实的示例,请参见@ShadowRanger提供的SQLAlchemy示例。还请注意,您__ne__在典型用例中的工作实现并不正确。波音737 MAX飞机在坠机前飞了500,000次航班…
Maggyero

-1

如果所有的__eq____ne____lt____ge____le__,和__gt__为班级意义,那么就实现__cmp__代替。否则,由于Daniel DiPaolo所说的话,请按照您的方式做(在我进行测试而不是查找时;))


12
__cmp__()Python 3.x不再支持该特殊方法,因此您应该习惯使用丰富的比较运算符。
Don O'Donnell

8
或者,如果您使用的是Python 2.7或3.x,则functools.total_ordering装饰器也非常方便。
亚当·帕金

感谢您的单挑。但是,在过去的一年半中,我已经沿着这些思路认识到很多事情。;)
Karl Knechtel 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.