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

我有一个要覆盖__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缺少使用这些运算符的方式而导致的一个好主意?

I have a class where I want to override the __eq__ method. It seems to make sense that I should override the __ne__ method as well, but does it make sense to implement __ne__ in terms of __eq__ as such?

class A:

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

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

Or is there something that I am missing with the way Python uses these methods that makes this not a good idea?


回答 0

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

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

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

Yes, that’s perfectly fine. In fact, the documentation urges you to define __ne__ when you define __eq__:

There are no implied relationships among the comparison operators. The truth of x==y does not imply that x!=y is false. Accordingly, when defining __eq__(), one should also define __ne__() so that the operators will behave as expected.

In a lot of cases (such as this one), it will be as simple as negating the result of __eq__, but not always.


回答 1

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

而不是__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编写底层逻辑。

Python, should I implement __ne__() operator based on __eq__?

Short Answer: Don’t implement it, but if you must, use ==, not __eq__

In Python 3, != is the negation of == by default, so you are not even required to write a __ne__, and the documentation is no longer opinionated on writing one.

Generally speaking, for Python 3-only code, don’t write one unless you need to overshadow the parent implementation, e.g. for a builtin object.

That is, keep in mind Raymond Hettinger’s comment:

The __ne__ method follows automatically from __eq__ only if __ne__ isn’t already defined in a superclass. So, if you’re inheriting from a builtin, it’s best to override both.

If you need your code to work in Python 2, follow the recommendation for Python 2 and it will work in Python 3 just fine.

In Python 2, Python itself does not automatically implement any operation in terms of another – therefore, you should define the __ne__ in terms of == instead of the __eq__. E.G.

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)`

See proof that

  • implementing __ne__() operator based on __eq__ and
  • not implementing __ne__ in Python 2 at all

provides incorrect behavior in the demonstration below.

Long Answer

The documentation for Python 2 says:

There are no implied relationships among the comparison operators. The truth of x==y does not imply that x!=y is false. Accordingly, when defining __eq__(), one should also define __ne__() so that the operators will behave as expected.

So that means that if we define __ne__ in terms of the inverse of __eq__, we can get consistent behavior.

This section of the documentation has been updated for Python 3:

By default, __ne__() delegates to __eq__() and inverts the result unless it is NotImplemented.

and in the “what’s new” section, we see this behavior has changed:

  • != now returns the opposite of ==, unless == returns NotImplemented.

instead of using the __eq__ method directly so that if self.__eq__(other) of a subclass returns NotImplemented for the type checked, Python will appropriately check other.__eq__(self) From the documentation:

The NotImplemented object

This type has a single value. There is a single object with this value. This object is accessed through the built-in name NotImplemented. Numeric methods and rich comparison methods may return this value if they do not implement the operation for the operands provided. (The interpreter will then try the reflected operation, or some other fallback, depending on the operator.) Its truth value is true.

When given a rich comparison operator, if they’re not the same type, Python checks if the other is a subtype, and if it has that operator defined, it uses the other‘s method first (inverse for <, <=, >= and >). If NotImplemented is returned, then it uses the opposite’s method. (It does not check for the same method twice.) Using the == operator allows for this logic to take place.


Expectations

Semantically, you should implement __ne__ in terms of the check for equality because users of your class will expect the following functions to be equivalent for all instances of 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

That is, both of the above functions should always return the same result. But this is dependent on the programmer.

Demonstration of unexpected behavior when defining __ne__ based on __eq__:

First the setup:

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."""

Instantiate non-equivalent instances:

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

Expected Behavior:

(Note: while every second assertion of each of the below is equivalent and therefore logically redundant to the one before it, I’m including them to demonstrate that order does not matter when one is a subclass of the other.)

These instances have __ne__ implemented with ==:

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

These instances, testing under Python 3, also work correctly:

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

And recall that these have __ne__ implemented with __eq__ – while this is the expected behavior, the implementation is incorrect:

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

Unexpected Behavior:

Note that this comparison contradicts the comparisons above (not wrong1 == wrong2).

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

and,

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

Don’t skip __ne__ in Python 2

For evidence that you should not skip implementing __ne__ in Python 2, see these equivalent objects:

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

The above result should be False!

Python 3 source

The default CPython implementation for __ne__ is in typeobject.c in object_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;

But the default __ne__ uses __eq__?

Python 3’s default __ne__ implementation detail at the C level uses __eq__ because the higher level == (PyObject_RichCompare) would be less efficient – and therefore it must also handle NotImplemented.

If __eq__ is correctly implemented, then the negation of == is also correct – and it allows us to avoid low level implementation details in our __ne__.

Using == allows us to keep our low level logic in one place, and avoid addressing NotImplemented in __ne__.

One might incorrectly assume that == may return NotImplemented.

It actually uses the same logic as the default implementation of __eq__, which checks for identity (see do_richcompare and our evidence below)

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

f = Foo()
f2 = Foo()

And the comparisons:

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

Performance

Don’t take my word for it, let’s see what’s more performant:

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

I think these performance numbers speak for themselves:

>>> 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

This makes sense when you consider that low_level_python is doing logic in Python that would otherwise be handled on the C level.

Response to some critics

Another answerer writes:

Aaron Hall’s implementation not self == other of the __ne__ method is incorrect as it can never return NotImplemented (not NotImplemented is False) and therefore the __ne__ method that has priority can never fall back on the __ne__ method that does not have priority.

Having __ne__ never return NotImplemented does not make it incorrect. Instead, we handle prioritization with NotImplemented via the check for equality with ==. Assuming == is correctly implemented, we’re done.

not self == other used to be the default Python 3 implementation of the __ne__ method but it was a bug and it was corrected in Python 3.4 on January 2015, as ShadowRanger noticed (see issue #21408).

Well, let’s explain this.

As noted earlier, Python 3 by default handles __ne__ by first checking if self.__eq__(other) returns NotImplemented (a singleton) – which should be checked for with is and returned if so, else it should return the inverse. Here is that logic written as a class 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

This is necessary for correctness for C level Python API, and it was introduced in Python 3, making

redundant. All relevant __ne__ methods were removed, including ones implementing their own check as well as ones that delegate to __eq__ directly or via == – and == was the most common way of doing so.

Is Symmetry Important?

Our persistent critic provides a pathological example to make the case for handling NotImplemented in __ne__, valuing symmetry above all else. Let’s steel-man the argument with a clear example:

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)

So, by this logic, in order to maintain symmetry, we need to write the complicated __ne__, regardless of Python version.

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)

Apparently we should give no mind that these instances are both equal and not equal.

I propose that symmetry is less important than the presumption of sensible code and following the advice of the documentation.

However, if A had a sensible implementation of __eq__, then we could still follow my direction here and we would still have symmetry:

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)

Conclusion

For Python 2 compatible code, use == to implement __ne__. It is more:

  • correct
  • simple
  • performant

In Python 3 only, use the low-level negation on the C level – it is even more simple and performant (though the programmer is responsible for determining that it is correct).

Again, do not write low-level logic in high level Python.


回答 2

仅作记录,一个规范正确且可交叉的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)。exceptions:如果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起作用,而仅能起作用在一种安排中。

Just for the record, a canonically correct and cross Py2/Py3 portable __ne__ would look like:

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

This works with any __eq__ you might define:

  • Unlike not (self == other), doesn’t interfere with in some annoying/complex cases involving comparisons where one of the classes involved doesn’t imply that the result of __ne__ is the same as the result of not on __eq__ (e.g. SQLAlchemy’s ORM, where both __eq__ and __ne__ return special proxy objects, not True or False, and trying to not the result of __eq__ would return False, rather than the correct proxy object).
  • Unlike not self.__eq__(other), this correctly delegates to the __ne__ of the other instance when self.__eq__ returns NotImplemented (not self.__eq__(other) would be extra wrong, because NotImplemented is truthy, so when __eq__ didn’t know how to perform the comparison, __ne__ would return False, implying that the two objects were equal when in fact the only object asked had no idea, which would imply a default of not equal)

If your __eq__ doesn’t use NotImplemented returns, this works (with meaningless overhead), if it does use NotImplemented sometimes, this handles it properly. And the Python version check means that if the class is import-ed in Python 3, __ne__ is left undefined, allowing Python’s native, efficient fallback __ne__ implementation (a C version of the above) to take over.


Why this is needed

Python overloading rules

The explanation of why you do this instead of other solutions is somewhat arcane. Python has a couple general rules about overloading operators, and comparison operators in particular:

  1. (Applies to all operators) When running LHS OP RHS, try LHS.__op__(RHS), and if that returns NotImplemented, try RHS.__rop__(LHS). Exception: If RHS is a subclass of LHS‘s class, then test RHS.__rop__(LHS) first. In the case of comparison operators, __eq__ and __ne__ are their own “rop”s (so the test order for __ne__ is LHS.__ne__(RHS), then RHS.__ne__(LHS), reversed if RHS is a subclass of LHS‘s class)
  2. Aside from the idea of the “swapped” operator, there is no implied relationship between the operators. Even for instance of the same class, LHS.__eq__(RHS) returning True does not imply LHS.__ne__(RHS) returns False (in fact, the operators aren’t even required to return boolean values; ORMs like SQLAlchemy intentionally do not, allowing for a more expressive query syntax). As of Python 3, the default __ne__ implementation behaves this way, but it’s not contractual; you can override __ne__ in ways that aren’t strict opposites of __eq__.

How this applies to overloading comparators

So when you overload an operator, you have two jobs:

  1. If you know how to implement the operation yourself, do so, using only your own knowledge of how to do the comparison (never delegate, implicitly or explicitly, to the other side of the operation; doing so risks incorrectness and/or infinite recursion, depending on how you do it)
  2. If you don’t know how to implement the operation yourself, always return NotImplemented, so Python can delegate to the other operand’s implementation

The problem with not self.__eq__(other)

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

never delegates to the other side (and is incorrect if __eq__ properly returns NotImplemented). When self.__eq__(other) returns NotImplemented (which is “truthy”), you silently return False, so A() != something_A_knows_nothing_about returns False, when it should have checked if something_A_knows_nothing_about knew how to compare to instances of A, and if it doesn’t, it should have returned True (since if neither side knows how to compare to the other, they’re considered not equal to one another). If A.__eq__ is incorrectly implemented (returning False instead of NotImplemented when it doesn’t recognize the other side), then this is “correct” from A‘s perspective, returning True (since A doesn’t think it’s equal, so it’s not equal), but it might be wrong from something_A_knows_nothing_about‘s perspective, since it never even asked something_A_knows_nothing_about; A() != something_A_knows_nothing_about ends up True, but something_A_knows_nothing_about != A() could False, or any other return value.

The problem with not self == other

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

is more subtle. It’s going to be correct for 99% of classes, including all classes for which __ne__ is the logical inverse of __eq__. But not self == other breaks both of the rules mentioned above, which means for classes where __ne__ isn’t the logical inverse of __eq__, the results are once again non-symmetric, because one of the operands is never asked if it can implement __ne__ at all, even if the other operand can’t. The simplest example is a weirdo class which returns False for all comparisons, so A() == Incomparable() and A() != Incomparable() both return False. With a correct implementation of A.__ne__ (one which returns NotImplemented when it doesn’t know how to do the comparison), the relationship is symmetric; A() != Incomparable() and Incomparable() != A() agree on the outcome (because in the former case, A.__ne__ returns NotImplemented, then Incomparable.__ne__ returns False, while in the latter, Incomparable.__ne__ returns False directly). But when A.__ne__ is implemented as return not self == other, A() != Incomparable() returns True (because A.__eq__ returns, not NotImplemented, then Incomparable.__eq__ returns False, and A.__ne__ inverts that to True), while Incomparable() != A() returns False.

You can see an example of this in action here.

Obviously, a class that always returns False for both __eq__ and __ne__ is a little strange. But as mentioned before, __eq__ and __ne__ don’t even need to return True/False; the SQLAlchemy ORM has classes with comparators that returns a special proxy object for query building, not True/False at all (they’re “truthy” if evaluated in a boolean context, but they’re never supposed to be evaluated in such a context).

By failing to overload __ne__ properly, you will break classes of that sort, as the code:

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

will work (assuming SQLAlchemy knows how to insert MyClassWithBadNE into a SQL string at all; this can be done with type adapters without MyClassWithBadNE having to cooperate at all), passing the expected proxy object to filter, while:

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

will end up passing filter a plain False, because self == other returns a proxy object, and not self == other just converts the truthy proxy object to False. Hopefully, filter throws an exception on being handled invalid arguments like False. While I’m sure many will argue that MyTable.fieldname should be consistently on the left hand side of the comparison, the fact remains that there is no programmatic reason to enforce this in the general case, and a correct generic __ne__ will work either way, while return not self == other only works in one arrangement.


回答 3

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

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,因此是不正确的。

Correct __ne__ implementation

@ShadowRanger’s implementation of the special method __ne__ is the correct one:

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

It also happens to be the default implementation of the special method __ne__ since Python 3.4, as stated in the Python documentation:

By default, __ne__() delegates to __eq__() and inverts the result unless it is NotImplemented.

Also note that returning the value NotImplemented for unsupported operands is not specific to the special method __ne__. In fact, all the special comparison methods1 and special numeric methods2 should return the value NotImplemented for unsupported operands, as specified in the Python documentation:

NotImplemented

This type has a single value. There is a single object with this value. This object is accessed through the built-in name NotImplemented. Numeric methods and rich comparison methods should return this value if they do not implement the operation for the operands provided. (The interpreter will then try the reflected operation, or some other fallback, depending on the operator.) Its truth value is true.

An example for the special numeric methods is given in the Python documentation:

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 The special comparison methods: __lt__, __le__, __eq__, __ne__, __gt__ and __ge__.

2 The special numeric methods: __add__, __sub__, __mul__, __matmul__, __truediv__, __floordiv__, __mod__, __divmod__, __pow__, __lshift__, __rshift__, __and__, __xor__, __or__ and their __r*__ reflected and __i*__ in-place counterparts.

Incorrect __ne__ implementation #1

@Falmarri’s implementation of the special method __ne__ is incorrect:

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

The problem with this implementation is that it does not fall back on the special method __ne__ of the other operand as it never returns the value NotImplemented (the expression not self.__eq__(other) evaluates to the value True or False, including when its subexpression self.__eq__(other) evaluates to the value NotImplemented since the expression bool(NotImplemented) evaluates to the value True). The Boolean evaluation of the value NotImplemented breaks the complement relationship between the comparison operators != and ==:

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

Incorrect __ne__ implementation #2

@AaronHall’s implementation of the special method __ne__ is also incorrect:

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

The problem with this implementation is that it directly falls back on the special method __eq__ of the other operand, bypassing the special method __ne__ of the other operand as it never returns the value NotImplemented (the expression not self == other falls back on the special method __eq__ of the other operand and evaluates to the value True or False). Bypassing a method is incorrect because that method may have side effects like updating the state of the object:

class Correct:

    def __init__(self):
        self.counter = 0

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


class Incorrect:

    def __init__(self):
        self.counter = 0

    def __ne__(self, other):
        self.counter += 1
        return not self == other


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

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

Understanding comparison operations

In mathematics, a binary relation R over a set X is a set of ordered pairs (xy) in X2. The statement (xy) in R reads “x is R-related to y” and is denoted by xRy.

Properties of a binary relation R over a set X:

  • R is reflexive when for all x in X, xRx.
  • R is irreflexive (also called strict) when for all x in X, not xRx.
  • R is symmetric when for all x and y in X, if xRy then yRx.
  • R is antisymmetric when for all x and y in X, if xRy and yRx then x = y.
  • R is transitive when for all x, y and z in X, if xRy and yRz then xRz.
  • R is connex (also called total) when for all x and y in X, xRy or yRx.
  • R is an equivalence relation when R is reflexive, symmetric and transitive.
    For example, =. However ≠ is only symmetric.
  • R is an order relation when R is reflexive, antisymmetric and transitive.
    For example, ≤ and ≥.
  • R is a strict order relation when R is irreflexive, antisymmetric and transitive.
    For example, < and >. However ≠ is only irreflexive.

Operations on two binary relations R and S over a set X:

  • The converse of R is the binary relation RT = {(yx) | xRy} over X.
  • The complement of R is the binary relation ¬R = {(xy) | not xRy} over X.
  • The union of R and S is the binary relation R ∪ S = {(xy) | xRy or xSy} over X.

Relationships between comparison relations that are always valid:

  • 2 complementary relationships: = and ≠ are each other’s complement;
  • 6 converse relationships: = is the converse of itself, ≠ is the converse of itself, < and > are each other’s converse, and ≤ and ≥ are each other’s converse;
  • 2 union relationships: ≤ is the union < and =, and ≥ is the union of > and =.

Relationships between comparison relations that are only valid for connex orders:

  • 4 complementary relationships: < and ≥ are each other’s complement, and > and ≤ are each other’s complement.

So to correctly implement in Python the comparison operators ==, !=, <, >, <=, and >= corresponding to the comparison relations =, ≠, <, >, ≤, and ≥, all the above mathematical properties and relationships should hold.

A comparison operation x operator y calls the special comparison method __operator__ of the class of one of its operands:

class X:

    def __operator__(self, other):
        # implementation

Since R is reflexive implies xRx, a reflexive comparison operation x operator y (x == y, x <= y and x >= y) or reflexive special comparison method call x.__operator__(y) (x.__eq__(y), x.__le__(y) and x.__ge__(y)) should evaluate to the value True if x and y are identical, that is if the expression x is y evaluates to True. Since R is irreflexive implies not xRx, an irreflexive comparison operation x operator y (x != y, x < y and x > y) or irreflexive special comparison method call x.__operator__(y) (x.__ne__(y), x.__lt__(y) and x.__gt__(y)) should evaluate to the value False if x and y are identical, that is if the expression x is y evaluates to True. The reflexive property is considered by Python for the comparison operator == and associated special comparison method __eq__ but surprisingly not considered for the comparison operators <= and >= and associated special comparison methods __le__ and __ge__, and the irreflexive property is considered by Python for the comparison operator != and associated special comparison method __ne__ but surprisingly not considered for the comparison operators < and > and associated special comparison methods __lt__ and __gt__. The ignored comparison operators instead raise the exception TypeError (and associated special comparison methods instead return the value NotImplemented), as explained in the Python documentation:

The default behavior for equality comparison (== and !=) is based on the identity of the objects. Hence, equality comparison of instances with the same identity results in equality, and equality comparison of instances with different identities results in inequality. A motivation for this default behavior is the desire that all objects should be reflexive (i.e. x is y implies x == y).

A default order comparison (<, >, <=, and >=) is not provided; an attempt raises TypeError. A motivation for this default behavior is the lack of a similar invariant as for equality. [This is incorrect since <= and >= are reflexive like ==, and < and > are irreflexive like !=.]

The class object provides the default implementations of the special comparison methods which are inherited by all its subclasses, as explained in the Python documentation:

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

These are the so-called “rich comparison” methods. The correspondence between operator symbols and method names is as follows: x<y calls x.__lt__(y), x<=y calls x.__le__(y), x==y calls x.__eq__(y), x!=y calls x.__ne__(y), x>y calls x.__gt__(y), and x>=y calls x.__ge__(y).

A rich comparison method may return the singleton NotImplemented if it does not implement the operation for a given pair of arguments.

[…]

There are no swapped-argument versions of these methods (to be used when the left argument does not support the operation but the right argument does); rather, __lt__() and __gt__() are each other’s reflection, __le__() and __ge__() are each other’s reflection, and __eq__() and __ne__() are their own reflection. If the operands are of different types, and right operand’s type is a direct or indirect subclass of the left operand’s type, the reflected method of the right operand has priority, otherwise the left operand’s method has priority. Virtual subclassing is not considered.

Since R = (RT)T, a comparison xRy is equivalent to the converse comparison yRTx (informally named “reflected” in the Python documentation). So there are two ways to compute the result of a comparison operation x operator y: calling either x.__operator__(y) or y.__operatorT__(x). Python uses the following computing strategy:

  1. It calls x.__operator__(y) unless the right operand’s class is a descendant of the left operand’s class, in which case it calls y.__operatorT__(x) (allowing classes to override their ancestors’ converse special comparison method).
  2. If the operands x and y are unsupported (indicated by the return value NotImplemented), it calls the converse special comparison method as a 1st fallback.
  3. If the operands x and y are unsupported (indicated by the return value NotImplemented), it raises the exception TypeError except for the comparison operators == and != for which it tests respectively the identity and non-identity of the operands x and y as a 2nd fallback (leveraging the reflexivity property of == and irreflexivity property of !=).
  4. It returns the result.

In CPython this is implemented in C code, which can be translated into Python code (with the names eq for ==, ne for !=, lt for <, gt for >, le for <= and ge for >=):

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

Since R = ¬(¬R), a comparison xRy is equivalent to the complement comparison ¬(x¬Ry). ≠ is the complement of =, so the special method __ne__ is implemented in terms of the special method __eq__ for supported operands by default, while the other special comparison methods are implemented independently by default (the fact that ≤ is the union of < and =, and ≥ is the union of > and = is surprisingly not considered, which means that currently the special methods __le__ and __ge__ should be user implemented), as explained in the Python documentation:

By default, __ne__() delegates to __eq__() and inverts the result unless it is NotImplemented. There are no other implied relationships among the comparison operators, for example, the truth of (x<y or x==y) does not imply x<=y.

In CPython this is implemented in C code, which can be translated into Python code:

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

So by default:

  • a comparison operation x operator y raises the exception TypeError except for the comparison operators == and != for which it returns respectively the identity and non-identity of the operands x and y;
  • a special comparison method call x.__operator__(y) returns the value NotImplemented except for the special comparison methods __eq__ and __ne__ for which it returns respectively True and False if the operands x and y are respectively identical and non-identical and the value NotImplemented otherwise.

回答 4

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

If all of __eq__, __ne__, __lt__, __ge__, __le__, and __gt__ make sense for the class, then just implement __cmp__ instead. Otherwise, do as you’re doing, because of the bit Daniel DiPaolo said (while I was testing it instead of looking it up ;) )


声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。