117

我有一个要覆盖该__eq__方法的类。__ne__我也应该重写该方法似乎是有道理的。我应该__ne__作为否定来实施__eq__还是一个坏主意?

class A:

    def __init__(self, state):
        self.state = state

    def __eq__(self, other):
        return self.state == other.state

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

5 回答 5

159

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

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

在 Python 3 中,默认情况下!=是否定的==,因此您甚至不需要编写 a __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。如果数值方法和富比较方法没有实现对提供的操作数的操作,则它们可能会返回此值。(然后解释器将尝试反射操作或其他一些回退操作,具体取决于操作员。)其真值为真。

当给定一个丰富的比较运算符时,如果它们不是同一类型,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__?

Python 3__ne__在 C 级别使用的默认实现细节是__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__不正确的,因为它永远无法返回NotImplemented( not NotImplementedis False),因此__ne__具有优先级的方法永远不会退回到__ne__没有优先级的方法上。

永不__ne__返回NotImplemented并不意味着它不正确。NotImplemented相反,我们通过检查与 的相等性来处理优先级==。假设==正确实施,我们就完成了。

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

好吧,让我们解释一下。

如前所述,Python 3 默认__ne__通过首先检查是否self.__eq__(other)返回NotImplemented(单例)来处理 - 应该检查是否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 中编写低级逻辑。

于 2015-06-05T21:41:16.803 回答
65

是的,这完全没问题。事实上,文档敦促您在定义__ne__时进行定义__eq__

比较运算符之间没有隐含的关系。的真理x==y并不意味着它x!=y 是错误的。因此,在定义 时 __eq__(),还应该定义__ne__()操作符的行为与预期一致。

在很多情况下(例如这种情况),它就像否定 的结果一样简单__eq__,但并非总是如此。

于 2010-12-04T06:26:22.893 回答
11

只是为了记录,一个规范正确和交叉 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), 不同的是,在涉及比较的一些烦人/复杂的情况下,其中一个所涉及的类并不意味着结果与on__ne__的结果相同(例如 SQLAlchemy 的 ORM,其中和返回特殊的代理对象, not or ,并尝试返回的结果,而不是正确的代理对象)。not__eq____eq____ne__TrueFalsenot__eq__False
  • 不同的是, this 在返回时not self.__eq__(other)正确地委托给__ne__另一个实例(将是额外错误的,因为它是真实的,所以当不知道如何执行比较时,将返回,这意味着这两个对象是相等的,而实际上是唯一的被问的对象不知道,这意味着默认不相等)self.__eq__NotImplementednot self.__eq__(other)NotImplemented__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__and__ne__是他们自己的“rop”(所以测试顺序__ne__LHS.__ne__(RHS), then ,如果是's 类的子类,则RHS.__ne__(LHS)相反)RHSLHS
  2. 除了“交换”运算符的想法之外,运算符之间没有隐含的关系。即使对于同一个类的实例,LHS.__eq__(RHS)返回True并不意味着LHS.__ne__(RHS)返回False(事实上,运算符甚至不需要返回布尔值;像 SQLAlchemy 这样的 ORM 故意不这样做,从而允许更具表现力的查询语法)。从 Python 3 开始,默认__ne__实现是这样的,但它不是契约性的;__ne__您可以以不严格相反的方式覆盖__eq__.

这如何适用于重载比较器

所以当你重载一个操作符时,你有两个工作:

  1. 如果您知道如何自己实现操作,请使用您自己关于如何进行比较的知识(永远不要隐式或显式委托给操作的另一端;这样做有不正确和/或无限递归的风险,取决于你怎么做)
  2. 如果您知道如何自己实现操作,请始终return NotImplemented,以便 Python 可以委托给其他操作数的实现

问题与not self.__eq__(other)

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

从不委托给另一方(如果__eq__正确返回则不正确NotImplemented)。当self.__eq__(other)返回时NotImplemented(这是“真实的”),你默默地返回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__,即使另一个操作数不能。最简单的例子是一个怪异类,它返回False所有比较,所以A() == Incomparable()A() != Incomparable()都返回False。通过正确实现A.__ne__NotImplemented当它不知道如何进行比较时返回),关系是对称的;A() != Incomparable()Incomparable() != A()就结果达成一致(因为在前一种情况下,A.__ne__返回NotImplemented,然后Incomparable.__ne__返回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只能工作在一种安排中。

于 2016-03-03T19:48:51.277 回答
7

正确执行__ne__

@ShadowRanger 对特殊方法的实现__ne__是正确的:

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

它也恰好是__ne__ Python 3.4 以来特殊方法的默认实现,如Python 文档中所述:

默认情况下,__ne__()委托__eq__()并反转结果,除非它是NotImplemented.

另请注意,返回NotImplemented不受支持的操作数的值并不特定于特殊方法__ne__。事实上,所有特殊比较方法1和特殊数值方法2都应该返回NotImplemented不受支持的操作数的值,如Python 文档中所述:

未实现

这种类型只有一个值。有一个具有此值的对象。该对象通过内置名称访问NotImplemented。如果数值方法和富比较方法没有实现对提供的操作数的操作,则应返回此值。(然后解释器将尝试反射操作或其他一些回退操作,具体取决于操作员。)其真值为真。

Python 文档中给出了特殊数值方法的示例:

class MyIntegral(Integral):

    def __add__(self, other):
        if isinstance(other, MyIntegral):
            return do_my_adding_stuff(self, other)
        elif isinstance(other, OtherTypeIKnowAbout):
            return do_my_other_adding_stuff(self, other)
        else:
            return NotImplemented

    def __radd__(self, other):
        if isinstance(other, MyIntegral):
            return do_my_adding_stuff(other, self)
        elif isinstance(other, OtherTypeIKnowAbout):
            return do_my_other_adding_stuff(other, self)
        elif isinstance(other, Integral):
            return int(other) + int(self)
        elif isinstance(other, Real):
            return float(other) + float(self)
        elif isinstance(other, Complex):
            return complex(other) + complex(self)
        else:
            return NotImplemented

1特殊比较方法:__lt__, __le__, __eq__, __ne__,__gt____ge__.

2特殊数值方法:__add__, __sub__, __mul__, __matmul__, __truediv__, __floordiv__, __mod__, __divmod__, __pow__, __lshift__, __rshift__, __and__, __xor__,__or__以及它们的__r*__反射和__i*__就地对应物。

__ne__#1的错误实现

@Falmarri 对特殊方法的实现__ne__不正确:

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

这个实现的问题是它不会依赖于__ne__另一个操作数的特殊方法,因为它从不返回值NotImplemented(表达式的not self.__eq__(other)计算结果为值TrueFalse,包括当它的子表达式self.__eq__(other)计算为值时,NotImplemented因为表达式bool(NotImplemented)计算为值True)。值的布尔求值NotImplemented打破了比较运算符和之间的补码关系:!===

class Correct:

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


class Incorrect:

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


x, y = Correct(), Correct()
assert (x != y) is not (x == y)

x, y = Incorrect(), Incorrect()
assert (x != y) is not (x == y)  # AssertionError

__ne__#2的错误实现

@AaronHall 对特殊方法的实现__ne__也不正确:

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

这个实现的问题是它直接依赖于__eq__另一个操作数的特殊方法,绕过另一个操作数的特殊方法__ne__,因为它从不返回值NotImplemented(表达式not self == other依赖于另一个操作数的特殊方法__eq__并计算为值TrueFalse)。绕过方法是不正确的,因为该方法可能会产生副作用,例如更新对象的状态:

class Correct:

    def __init__(self):
        self.state = False

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


class Incorrect:

    def __init__(self):
        self.state = False

    def __ne__(self, other):
        self.state = True
        return not self == other


x, y = Correct(), Correct()
assert x != y
assert x.state == y.state

x, y = Incorrect(), Incorrect()
assert x != y
assert x.state == y.state  # AssertionError

了解比较操作

在数学中,集合X上的二元关系 R是X 2中的一组有序对 ( xy )  。R中的语句 ( xy ) 读作 '<em>x is R -related to y ' 并用xRy表示。

集合X上的二元关系R的性质:

  • 当对于X中的所有x时, R自反的,xRx
  • 当对于X中的所有x而不是xRx时, R是不自反的(也称为严格) 。
  • 当对于X中的所有xy时, R对称的,如果xRyyRx
  • 当对于X中的所有xy时, R反对称的,如果xRyyRxx  =  y
  • 当对于 X中的所有xyz时, R是可传递的,如果xRyyRzxRz
  • 当对于XxRyyRx中的所有xy时, Rconnex(也称为total ) 。
  • R是自反的、对称的和传递的时, R等价关系。 例如,=。然而≠只是对称的。
  • R是自反的、反对称的和传递的时, R是一个顺序关系。 例如,≤ 和 ≥。
  • R是反自反的、反对称的和传递的时, R是一个严格的序关系。 例如,< 和 >。然而≠只是非自反的。

对集合X上的两个二元关系RS的操作:

  • R是二元关系R T  = {( yx ) | xRy } 超过X
  • R补码是二元关系 ¬ R  = {( xy ) | 不是xRy } 超过X
  • RS的并是二元关系R  ∪ <em>S = {( xy ) | xRyxSy } 在X上。

始终有效的比较关系之间的关系:

  • 2 互补关系:=和≠互为互补;
  • 6种逆关系:=为自身逆,≠自身逆,<和>互为逆,≤和≥互为逆;
  • 2个并集关系:≤是<和=的并集,≥是>和=的并集。

仅对连接顺序关系有效的比较关系之间的关系:

  • 4种互补关系:<和≥互为补码,>和≤互为补码。

所以要在 Python 中正确实现比较运算符==, !=, <, >, <=, 和>=对应比较关系 =, ≠, <, >, ≤, 和 ≥,以上所有数学性质和关系都应该成立。

比较操作x operator y调用__operator__其操作数之一的类的特殊比较方法:

class X:

    def __operator__(self, other):
        # implementation

由于R自反的,意味着xRx,自反比较操作x operator y( x == y,x <= yx >= y) 或自反特殊比较方法调用x.__operator__(y)( x.__eq__(y),x.__le__(y)x.__ge__(y)) 的计算结果应为True如果xy相同,即如果表达式x is y计算结果为True。由于R是非自反意味着不是xRx,因此非自反比较操作x operator y( x != y,x < yx > y) 或非自反特殊比较方法调用x.__operator__(y)( x.__ne__(y),x.__lt__(y)x.__gt__(y)) 应该评估为该值False如果xy相同,即如果表达式的x is y计算结果为True。Python 为比较运算符==和相关的特殊比较方法考虑了自反属性,__eq__令人惊讶的是,比较运算符和相关的特殊比较方法和 没有考虑自反<=属性>=__le__而Python 为比较运算符和相关的特殊比较方法__ge__考虑了非自反属性,但是令人惊讶的是,没有考虑比较运算符和相关的特殊比较方法和!=__ne__<>__lt____gt__. 被忽略的比较运算符会引发异常TypeError(并且相关的特殊比较方法会返回值NotImplemented),如Python 文档中所述:

==相等比较 (和)的默认行为!=基于对象的标识。因此,具有相同身份的实例的相等比较导致相等,而具有不同身份的实例的相等比较导致不平等。这种默认行为的动机是希望所有对象都应该是自反的(即x is y暗示x == y)。

未提供默认顺序比较(<><=>=);一次尝试提出TypeError。这种默认行为的动机是缺乏与相等性类似的不变量。[这是不正确的,因为<=>=是反身的==,并且<>是反身的!=。]

该类object提供了由其所有子类继承的特殊比较方法的默认实现,如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没有为给定的一对参数实现操作,则它可能会返回单例。

[…]

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

由于R = ( R T ) T,比较xRy等价于比较yR T x(在 Python 文档中非正式地命名为“反射”)。所以有两种方法可以计算比较操作的结果x operator y:调用x.__operator__(y)y.__operatorT__(x)。Python 使用以下计算策略:

  1. x.__operator__(y)除非右操作数的类是左操作数类的后代,否则它会调用,在这种情况下它会调用y.__operatorT__(x)允许类覆盖其祖先的逆特殊比较方法)。
  2. 如果操作数xy不受支持(由返回值指示NotImplemented),它将调用逆特殊比较方法作为第一个后备
  3. 如果操作数xy不受支持(由返回值指示NotImplemented),则引发异常TypeError,比较运算符除外==!=它分别比较操作数的身份和非身份,xy作为第二个后备(利用和的自反性属性==)的非自反性性质!=
  4. 它返回结果。

在 CPython中,这是用 C 代码实现的,可以将其翻译成 Python 代码(名称eq为 for ==nefor !=ltfor <gtfor >lefor<=gefor >=):

def 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 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 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__}' "
            f"and '{type(right).__name__}'"
        )
    return result
def 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__}' "
            f"and '{type(right).__name__}'"
        )
    return result
def 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__}' "
            f"and '{type(right).__name__}'"
        )
    return result
def 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__}' "
            f"and '{type(right).__name__}'"
        )
    return result

由于R = ¬(¬ R ),比较xRy等价于补码比较 ¬( x ¬ Ry )。≠ 是 = 的补码,所以特殊方法默认按照支持操作数__ne__的特殊方法__eq__实现,而其他特殊比较方法默认独立实现(事实上 ≤ 是 < 和 = 的并集,且≥ 是 > 和 = 的联合令人惊讶地没有考虑,这意味着当前特殊方法应该由用户实现),如__le__Python文档中所述:__ge__

默认情况下,__ne__()委托__eq__()并反转结果,除非它是NotImplemented. 比较运算符之间没有其他隐含关系,例如,真值(x<y or x==y)不隐含x<=y

在 CPython中,这是用 C 代码实现的,可以翻译成 Python 代码:

def __eq__(self, other):
    return self is other or 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

所以默认情况下:

  • 比较操作x operator y引发异常TypeError,除了比较运算符==!=它分别返回值TrueFalse如果操作数xy分别相同和不同,以及值FalseTrue否则;
  • 特殊比较方法调用x.__operator__(y)返回NotImplemented除特殊比较方法之外__eq__的值__ne__,它分别返回值TrueFalse如果操作数xy分别相同和不相同,NotImplemented否则返回值。
于 2018-06-02T22:25:10.780 回答
-1

如果所有__eq__, __ne__, __lt__, __ge__,__le____gt__对类都有意义,那么只需实现即可__cmp__。否则,按照你正在做的事情做,因为 Daniel DiPaolo 说过的话(当我测试它而不是查找它时;))

于 2010-12-04T06:28:43.920 回答