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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4352244/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-18 15:22:36  来源:igfitidea点击:

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

pythoncomparisonoperatorspython-datamodel

提问by Falmarri

I have a class where I want to override the __eq__()operator. It seems to make sense that I should override the __ne__()operator as well, but does it make sense to implement __ne__based on __eq__as such?

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

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

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

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

或者我在 Python 使用这些运算符的方式中遗漏了什么,这使得这不是一个好主意?

采纳答案by Daniel DiPaolo

Yes, that's perfectly fine. In fact, the documentationurges you to define __ne__when you define __eq__:

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

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

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

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

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

回答by Karl Knechtel

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

如果所有__eq__, __ne__, __lt__, __ge__, __le__, 和__gt__对类有意义,那么只需实现即可__cmp__。否则,照你所做的去做,因为 Daniel DiPaolo 说的那一点(当我正在测试它而不是查找它时;))

回答by Aaron Hall

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

Python,我应该__ne__()基于__eq__?

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

简短回答:不要实现它,但如果必须,请使用==,而不是__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.

在 Python 3 中,!===默认情况下的否定,因此您甚至不需要编写__ne__,并且文档不再固执于编写一个。

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.

一般来说,对于只有 Python 3 的代码,除非您需要掩盖父实现,例如内置对象,否则不要编写代码。

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

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

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.

仅当尚未在超类中定义时,该__ne__方法__eq__才会 自动跟随__ne__。因此,如果您从内置函数继承,最好同时覆盖两者。

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.

如果您需要您的代码在 Python 2 中工作,请遵循 Python 2 的建议,它可以在 Python 3 中正常工作。

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.

在 Python 2 中,Python 本身不会根据另一个自动实现任何操作 - 因此,您应该定义__ne__in==而不是__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)`

See proof that

看到证明

  • implementing __ne__()operator based on __eq__and
  • not implementing __ne__in Python 2 at all
  • 实现__ne__()运算符基于__eq__
  • 根本没有__ne__在 Python 2 中实现

provides incorrect behavior in the demonstration below.

在下面的演示中提供了不正确的行为。

Long Answer

长答案

The documentationfor Python 2 says:

Python 2的文档说:

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

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

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

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

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

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

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

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

and in the "what's new" section, we see this behavior has changed:

“新增内容”部分,我们看到这种行为发生了变化:

  • !=now returns the opposite of ==, unless ==returns NotImplemented.
  • !=现在返回相反的==, 除非==返回NotImplemented

For implementing __ne__, we prefer to use the ==operatorinstead of using the __eq__method directly so that if self.__eq__(other)of a subclass returns NotImplementedfor the type checked, Python will appropriately check other.__eq__(self)From the documentation:

对于实现__ne__,我们更喜欢使用==操作符而不是__eq__直接使用方法,这样如果self.__eq__(other)子类返回NotImplemented检查的类型,Python 将适当地other.__eq__(self)从文档中检查:

The NotImplementedobject

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.

NotImplemented对象

这种类型只有一个值。有一个具有此值的对象。这个对象是通过内置的 name 访问的 NotImplemented。如果数值方法和富比较方法没有实现对提供的操作数的操作,它们可能会返回此值。(然后解释器将尝试反射操作,或其他一些回退,这取决于操作符。)它的真值是真的。

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

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



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

从语义上讲,您应该__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

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

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

Demonstration of unexpected behavior when defining __ne__based on __eq__:

__ne__基于以下定义时的意外行为演示__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 ==:

这些实例已__ne__实现==

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

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

这些在 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

And recall that these have __ne__implemented with __eq__- while this is the expected behavior, the implementation is incorrect:

回想一下,这些已经__ne__实现了__eq__- 虽然这是预期的行为,但实现是不正确的:

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

请注意,此比较与上面的比较 ( 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

不要跳过__ne__Python 2

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

有关您不应跳过__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

The above result should be False!

上面的结果应该是False

Python 3 source

Python 3 源代码

The default CPython implementation for __ne__is in typeobject.cin object_richcompare:

默认的 CPython 实现__ne__typeobject.cinobject_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__?

但默认__ne__使用__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.

Python 3__ne__在 C 级别使用的默认实现细节是__eq__因为更高级别==PyObject_RichCompare)效率较低 - 因此它也必须处理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__.

如果__eq__正确实现,那么 的否定==也是正确的 - 它允许我们避免在我们的__ne__.

Using ==allows us to keep our low level logic in oneplace, and avoidaddressing NotImplementedin __ne__.

使用==使我们能够保持我们的逻辑低电平一个地方,避免处理NotImplemented__ne__

One might incorrectly assume that ==may return NotImplemented.

人们可能会错误地认为==可能返回NotImplemented

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

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

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_pythonis doing logic in Python that would otherwise be handled on the C level.

当您认为这low_level_python是在 Python 中执行逻辑,否则将在 C 级别处理时,这是有道理的。

Response to some critics

对一些批评的回应

Another answerer writes:

另一位答主写道:

Aaron Hall's implementation not self == otherof the __ne__method is incorrect as it can never return NotImplemented(not NotImplementedis False) and therefore the __ne__method that has priority can never fall back on the __ne__method that does not have priority.

Aaron Hallnot self == other对该__ne__方法的实现是不正确的,因为它永远不会返回NotImplementednot NotImplementedis False),因此__ne__具有优先级的方法永远不会回退到__ne__没有优先级的方法上。

Having __ne__never return NotImplementeddoes not make it incorrect. Instead, we handle prioritization with NotImplementedvia the check for equality with ==. Assuming ==is correctly implemented, we're done.

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

not self == otherused 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).

not self == other曾经是该方法的默认 Python 3 实现,__ne__但它是一个错误,并于 2015 年 1 月在 Python 3.4 中得到纠正,正如 ShadowRanger 所注意到的(参见问题 #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 isand returned if so, else it should return the inverse. Here is that logic written as a class mixin:

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

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

这对于 C 级 Python API 的正确性是必要的,它是在 Python 3 中引入的,使得

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.

多余的。所有相关的__ne__方法都被删除了,包括实现自己检查的方法以及__eq__直接或通过委托的方法==- 这==是最常见的方法。

Is Symmetry Important?

对称重要吗?

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

我们坚持不懈的批评家提供了一个病态的例子来说明处理NotImplementedin的情况__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)

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

所以,按照这个逻辑,为了保持对称性,我们需要编写复杂的__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)

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:

然而,如果 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)

Conclusion

结论

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

对于 Python 2 兼容代码,使用==来实现__ne__. 更多的是:

  • correct
  • simple
  • performant
  • 正确的
  • 简单的
  • 执行力

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

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

Again, do notwrite low-level logic in high level Python.

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

回答by ShadowRanger

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

只是为了记录,规范正确和跨 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

This works with any __eq__you might define:

这适用于__eq__您可能定义的任何内容:

  • 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 noton __eq__(e.g. SQLAlchemy's ORM, where both __eq__and __ne__return special proxy objects, not Trueor False, and trying to notthe 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 NotImplementedis 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)
  • not (self == other),不同,在一些涉及比较的烦人/复杂情况下不会干扰,其中所涉及的类之一并不暗示__ne__的结果与noton的结果相同__eq__(例如 SQLAlchemy 的 ORM,其中__eq__和 都__ne__返回特殊的代理对象,没有TrueFalse,并尝试not的结果__eq__将返回False,而不是正确的代理对象)。
  • 与 不同not self.__eq__(other),这__ne__self.__eq__返回时正确地委托给另一个实例的 的NotImplemented(not self.__eq__(other)将是额外的错误,因为它NotImplemented是真实的,所以当__eq__不知道如何执行比较时,__ne__将返回False,暗示这两个对象是相等的,而实际上是唯一的被问到的对象不知道,这意味着默认值不相等)

If your __eq__doesn't use NotImplementedreturns, this works (with meaningless overhead), if it does use NotImplementedsometimes, 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.

如果您__eq__不使用NotImplemented返回,这会起作用(具有无意义的开销),如果它NotImplemented有时确实使用,则可以正确处理它。Python 版本检查意味着如果该类import在 Python 3 中被-ed,__ne__则未定义,允许 Python 的本机、高效回退__ne__实现(上述 C 版本)接管。



Why this is needed

为什么需要这个

Python overloading rules

Python 重载规则

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:

为什么要这样做而不是其他解决方案的解释有些神秘。Python 有一些关于重载运算符的一般规则,特别是比较运算符:

  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 RHSis 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 RHSis 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 Truedoes 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__.
  1. (适用于所有运算符)运行时LHS OP RHS,尝试LHS.__op__(RHS),如果返回NotImplemented,则尝试RHS.__rop__(LHS)。例外:如果RHS是 的类的子LHS类,则RHS.__rop__(LHS)测试。在比较运算符的情况下,__eq__and__ne__是它们自己的“rop”(因此,如果是's 类的子类,则的测试顺序__ne__LHS.__ne__(RHS),则RHS.__ne__(LHS)相反)RHSLHS
  2. 除了“交换”运算符的想法之外,运算符之间没有隐含的关系。即使对于同一个类,LHS.__eq__(RHS)返回True并不意味着LHS.__ne__(RHS)返回False(事实上​​,运算符甚至不需要返回布尔值;像 SQLAlchemy 这样的 ORM 故意不这样做,允许更具表现力的查询语法)。从 Python 3 开始,默认__ne__实现的行为就是这样,但它不是契约性的;您可以__ne__以与__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 onlyyour 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'tknow how to implement the operation yourself, alwaysreturn NotImplemented, so Python can delegate to the other operand's implementation
  1. 如果您知道如何自己实现操作,那么这样做,使用您自己对如何进行比较的知识(不要隐式或显式地委托给操作的另一方;这样做有不正确和/或无限递归的风险,取决于你怎么做)
  2. 如果你知道如何自己实现操作,总是return NotImplemented,这样 Python 就可以委托给另一个操作数的实现

The problem with not self.__eq__(other)

问题在于 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_aboutreturns False, when it should have checked if something_A_knows_nothing_aboutknew 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 Falseinstead of NotImplementedwhen it doesn't recognize the other side), then this is "correct" from A's perspective, returning True(since Adoesn'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_aboutends up True, but something_A_knows_nothing_about != A()could False, or any other return value.

从不委托给另一方(如果__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,或任何其他返回值。

The problem with not self == other

问题在于 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 == otherbreaks both of the rules mentioned above, which means for classes where __ne__isn'tthe 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 Falsefor allcomparisons, so A() == Incomparable()and A() != Incomparable()both return False. With a correct implementation of A.__ne__(one which returns NotImplementedwhen 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 Falsedirectly). 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.

更微妙。它对 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.

You can see an example of this in action here.

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

Obviously, a class that always returns Falsefor 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/Falseat all (they're "truthy" if evaluated in a boolean context, but they're never supposed to be evaluated in such a context).

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

By failing to overload __ne__properly, you willbreak classes of that sort, as the code:

由于未能__ne__正确重载,您破坏那种类,如代码:

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

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

将工作(假设 SQLAlchemy 完全知道如何插入MyClassWithBadNESQL 字符串;这可以通过类型适配器完成而MyClassWithBadNE根本无需合作),将预期的代理对象传递给filter,同时:

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

will end up passing filtera plain False, because self == otherreturns a proxy object, and not self == otherjust converts the truthy proxy object to False. Hopefully, filterthrows an exception on being handled invalid arguments like False. While I'm sure many will argue that MyTable.fieldnameshouldbe 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 == otheronly works in one arrangement.

最终会传递filter一个普通的False,因为self == other返回一个代理对象,并且not self == other只是将真实的代理对象转换为False. 希望filter在处理无效参数时抛出异常,例如False. 虽然我相信很多人会争辩说MyTable.fieldname应该始终在比较的左侧,但事实仍然是在一般情况下没有程序上的原因来强制执行此操作,并且正确的泛型__ne__将在任何一种情况下都有效,而return not self == other仅有效在一种安排中。

回答by Maggyero

Short answer: yes (but read the documentation to do it right)

简短回答:是(但请阅读文档以正确执行)

ShadowRanger's implementation of the __ne__method is the correct one (and it happens to be the default implementation of the __ne__method since Python 3.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

Why? Because it keeps an important mathematical property, the symmetryof the !=operator. This operator is binaryso its result should depend on the dynamic type of bothoperands, not just one. This is implemented via double dispatchfor programming languages allowing multiple dispatch(such as Julia). In Python which only allows single dispatch, double dispatch is simulated for numeric methodsand rich comparison methodsby returning the value NotImplementedin the implementing methods which do not support the type of the other operand; the interpreter will then try the reflected method of the other operand.

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

Aaron Hall's implementation not self == otherof the __ne__method is incorrect as it removes the symmetry of the !=operator. Indeed, it can never return NotImplemented(not NotImplementedis False) and therefore the __ne__method with higher priority can never fall back on the __ne__method with lower priority. not self == otherused to be the default Python 3 implementation of the __ne__method but it was a bug which was corrected in Python 3.4 on January 2015, as ShadowRanger noticed (see issue #21408).

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

Implementation of the comparison operators

比较运算符的实现

The Python Language Referencefor Python 3 states in its chapter III Data model:

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)

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

A rich comparison method may return the singleton NotImplementedif 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.

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<ycalls x.__lt__(y)x<=ycalls x.__le__(y)x==ycalls x.__eq__(y)x!=ycalls x.__ne__(y)x>ycalls x.__gt__(y)x>=ycalls x.__ge__(y)

如果富比较方法NotImplemented没有为给定的参数对实现操作,它可能会返回单例。

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

Translating this into Python code gives (using operator_eqfor ==, operator_nefor !=, operator_ltfor <, operator_gtfor >, operator_lefor <=and operator_gefor >=):

将其转换为 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

Default implementation of the comparison methods

比较方法的默认实现

The documentation adds:

文档补充说:

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.

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

The default implementation of the comparison methods (__eq__, __ne__, __lt__, __gt__, __le__and __ge__) can thus be given by:

的比较方法的缺省的实现(__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

So this is the correct implementation of the __ne__method. And it does not always return the inverse of the __eq__method because when the __eq__method returns NotImplemented, its inverse not NotImplementedis False(as bool(NotImplemented)is True) instead of the desired NotImplemented.

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

Incorrect implementations of __ne__

不正确的实现 __ne__

As Aaron Hall demonstrated above, not self.__eq__(other)is not the default implementation of the __ne__method. But nor is not self == other.The latter is demonstrated below by comparing the behavior of the default implementation with the behavior of the not self == otherimplementation in two cases:

正如上面 Aaron Hall 演示的那样,not self.__eq__(other)不是该__ne__方法的默认实现。但也不是not self == other下面通过比较默认实现的行为与not self == other两种情况下实现的行为来演示后者:

  • the __eq__method returns NotImplemented;
  • the __eq__method returns a value different from NotImplemented.
  • __eq__方法返回NotImplemented
  • __eq__方法返回一个不同于 的值NotImplemented

Default implementation

默认实现

Let's see what happens when the A.__ne__method uses the default implementation and the A.__eq__method returns NotImplemented:

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

class A:
    pass


class B:

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


assert (A() != B()) == "B.__ne__"
  1. !=calls A.__ne__.
  2. A.__ne__calls A.__eq__.
  3. A.__eq__returns NotImplemented.
  4. !=calls B.__ne__.
  5. B.__ne__returns "B.__ne__".
  1. !=调用A.__ne__
  2. A.__ne__调用A.__eq__
  3. A.__eq__返回NotImplemented
  4. !=调用B.__ne__
  5. B.__ne__返回"B.__ne__"

This shows that when the A.__eq__method returns NotImplemented, the A.__ne__method falls back on the B.__ne__method.

这表明当A.__eq__方法返回时NotImplementedA.__ne__方法回退到B.__ne__方法上。

Now let's see what happens when the A.__ne__method uses the default implementation and the A.__eq__method returns a value different from NotImplemented:

现在让我们看看当该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. !=calls A.__ne__.
  2. A.__ne__calls A.__eq__.
  3. A.__eq__returns True.
  4. !=returns not True, that is False.
  1. !=调用A.__ne__
  2. A.__ne__调用A.__eq__
  3. A.__eq__返回True
  4. !=返回not True,即False

This shows that in this case, the A.__ne__method returns the inverse of the A.__eq__method. Thus the __ne__method behaves like advertised in the documentation.

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

Overriding the default implementation of the A.__ne__method with the correct implementation given above yields the same results.

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

not self == otherimplementation

not self == other执行

Let's see what happens when overriding the default implementation of the A.__ne__method with the not self == otherimplementation and the A.__eq__method returns NotImplemented:

让我们来看看重写的默认实现时,会发生什么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. !=calls A.__ne__.
  2. A.__ne__calls ==.
  3. ==calls A.__eq__.
  4. A.__eq__returns NotImplemented.
  5. ==calls B.__eq__.
  6. B.__eq__returns NotImplemented.
  7. ==returns A() is B(), that is False.
  8. A.__ne__returns not False, that 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

The default implementation of the __ne__method returned "B.__ne__", not True.

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

Now let's see what happens when overriding the default implementation of the A.__ne__method with the not self == otherimplementation and the A.__eq__method returns a value different from NotImplemented:

现在让我们看看重写的默认实现时,会发生什么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. !=calls A.__ne__.
  2. A.__ne__calls ==.
  3. ==calls A.__eq__.
  4. A.__eq__returns True.
  5. A.__ne__returns not True, that is False.
  1. !=调用A.__ne__
  2. A.__ne__调用==
  3. ==调用A.__eq__
  4. A.__eq__返回True
  5. A.__ne__返回not True,即False

The default implementation of the __ne__method also returned Falsein this case.

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

Since this implementation fails to replicate the behavior of the default implementation of the __ne__method when the __eq__method returns NotImplemented, it is incorrect.

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