__ne__ 应该作为 __eq__ 的否定来实现吗?

发布于 2024-10-06 04:04:32 字数 385 浏览 0 评论 0 原文

我有一个类,我想重写 __eq__ 方法。我也应该重写 __ne__ 方法,这似乎是有道理的。我应该将 __ne__ 实现为 __eq__ 的否定,还是一个坏主意?

class A:

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

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

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

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. Should I implement __ne__ as the negation of __eq__ as such or is it a bad idea?

class A:

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

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

木緿 2024-10-13 04:04:32

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

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

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

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

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

仅当满足以下条件时,__ne__ 方法才会自动遵循 __eq__
__ne__ 尚未在超类中定义。所以,如果你是
从内置继承,最好覆盖两者。

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

在 Python 2 中,Python 本身不会自动实现任何根据另一个操作的操作 - 因此,您应该根据 == 定义 __ne__ 而不是 __eq__
EG

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

请参阅以下证明,证明

  • 基于 __eq__ 实现 __ne__() 运算符,并且
  • 在 Python 2 中根本不实现 __ne__

会提供不正确的行为。

长答案

Python 2 的 文档 说:

比较运算符之间没有隐含关系。这
x==y 为真并不意味着 x!=y 为假。因此,当
定义__eq__(),还应该定义__ne__(),以便
操作员将按预期行事。

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

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

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

“新增内容”部分中,我们看到这种行为已经改变:

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

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

NotImplemented 对象

该类型只有一个值。有一个具有该值的对象。通过内置名称访问该对象
未实现。数值方法和丰富的比较方法可能会返回
如果他们没有实现操作数的操作,则为该值
假如。 (然后解释器将尝试反射操作,或者
其他一些后备,取决于操作员。)它的真值是
正确。

当给定丰富的比较运算符时,如果它们不是同一类型,Python 会检查 other 是否是子类型,如果定义了该运算符,则使用 other > 的方法优先(与 <<=>=> 相反) 。如果返回NotImplemented它使用相反的方法。 (它不会检查同一个方法两次。)使用== 运算符可以实现此逻辑。


期望

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

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

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

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

基于 __eq__ 定义 __ne__ 时意外行为的演示:

首先设置:

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__== 实现的 code>:

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

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

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

意外的行为:

请注意,此比较与上面的比较相矛盾(不是错误 1 ​​== 错误 2)。

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

不要在 Python 2 中跳过 __ne__

有关您不应在 Python 2 中跳过实现 __ne__ 的证据,请参阅这些等效对象:

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

上面的结果应该是 错误!

Python 3 源

__ne__ 的默认 CPython 实现位于 object_richcompare中的typeobject.c

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

但是默认的__ne__使用__eq__

Python 3 在 C 级别的默认 __ne__ 实现细节使用 __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 的 __ne__ 方法的实现 not self == other 是不正确的,因为它永远无法返回 NotImplementednot NotImplemented False),因此具有优先级的 __ne__ 方法永远不会依赖于不具有优先级的 __ne__ 方法。

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

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

好吧,让我们解释一下。

如前所述,Python 3 默认情况下通过首先检查 self.__eq__(other) 是否返回 NotImplemented (单例)来处理 __ne__ - 这应该使用 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__ 的方法 - 和 < code>== 是最常见的方法。

对称性重要吗?

我们坚持不懈的批评家提供了一个病态的例子来证明在 __ne__ 中处理 NotImplemented 的情况,重视对称性高于一切。让我们用一个清晰​​的例子来论证这个论点:

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.

For implementing __ne__, we prefer to use the == operator 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.

北恋 2024-10-13 04:04:32

是的,那很好。事实上,文档敦促您定义__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.

谜兔 2024-10-13 04:04:32

仅供记录,规范正确且跨 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 on __eq__ (例如 SQLAlchemy 的 ORM,其中 __eq____ne__ 返回特殊代理对象,而不是 TrueFalse,并且尝试 __eq__ 的结果将返回 False,而不是正确的代理对象)。
  • not self.__eq__(other) 不同,当 self.__eq__ 返回 NotImplemented< 时,这会正确委托给另一个实例的 __ne__ /code> (not self.__eq__(other) 会是额外的错误,因为 NotImplemented 是真的,所以当 __eq__ 不知道如何时要执行比较,__ne__ 将返回 False,这意味着两个对象相等,而实际上唯一询问的对象并不知道,这意味着默认值不相等)

如果您的 __eq__ 不使用 NotImplemented 返回,则此方法有效(具有无意义的开销),如果它有时确实使用 NotImplemented ,则此方法可以处理它适当地。 Python 版本检查意味着,如果该类在 Python 3 中被 import-ed,则 __ne__ 未定义,从而允许 Python 原生、高效的回退 __ne__ 实现(上述的 C 版本)接管。


为什么需要这样做

Python 重载规则

为什么这样做而不是其他解决方案的解释有点神秘。 Python 对于重载运算符(尤其是比较运算符)有一些一般规则:(

  1. 适用于所有运算符)运行 LHS OP RHS 时,尝试 LHS.__op__(RHS),如果返回 NotImplemented,请尝试 RHS.__rop__(LHS)。例外:如果 RHSLHS 类的子类,则先测试 RHS.__rop__(LHS) 首先 。对于比较运算符,__eq____ne__ 是它们自己的“rop”(因此 __ne__ 的测试顺序是 LHS .__ne__(RHS),然后是 RHS.__ne__(LHS),如果 RHSLHS 类的子类,则相反)
  2. 除了“交换”运算符的想法之外,运算符之间没有隐含的关系。即使对于同一类的实例,LHS.__eq__(RHS) 返回 True 并不意味着 LHS.__ne__(RHS) 返回 False(事实上,运算符甚至不需要返回布尔值;像 SQLAlchemy 这样的 ORM 故意不这样做,从而允许更具表现力的查询语法)。从 Python 3 开始,默认的 __ne__ 实现就以这种方式运行,但它不是契约性的;您可以以与 __eq__ 不严格相反的方式覆盖 __ne__

这如何适用于重载比较器

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

  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__,即使另一个操作数不能。最简单的例子是一个奇怪的类,它对所有比较返回False,因此A() == Incomparable()A( ) != Incomparable() 均返回 False。通过正确实现 A.__ne__(当不知道如何进行比较时返回 NotImplemented),这种关系是对称的; A() != Incomparable()Incomparable() != A() 就结果达成一致(因为在前一种情况下,A.__ne__ code> 返回 NotImplemented,然后 Incomparable.__ne__ 返回 False,而后者返回 Incomparable.__ne__ <直接代码>False)。但是,当 A.__ne__ 实现为 return not self == other 时,A() != Incomparable() 返回 True< /code> (因为 A.__eq__ 返回,而不是 NotImplemented,然后 Incomparable.__eq__ 返回 False,并且 < code>A.__ne__ 将其反转为 True),而 Incomparable() != A() 返回 False。

您可以查看实际操作示例 此处

显然,对于 __eq____ne__ 总是返回 False 的类有点奇怪。但如前所述,__eq____ne__ 甚至不需要返回 True/False; SQLAlchemy ORM 具有带有比较器的类,该比较器返回用于查询构建的特殊代理对象,而不是 True/False (如果在布尔上下文中评估,它们是“true” ,但它们永远不应该在这样的上下文中进行评估)。

如果无法正确重载 __ne__ ,您将破坏此类类,因为代码:

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

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

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

:传递 filter 一个普通的 False,因为 self == other 返回一个代理对象,并且不是 self == other只是将 true 代理对象转换为 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.

美人迟暮 2024-10-13 04:04:32

__ne__ 的正确实现

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

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

它也恰好是特殊方法 __ne__ 的默认实现> 自 Python 3.4 起,如 Python 文档

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

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

未实现

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

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

class MyIntegral(Integral):

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

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

1特殊比较方法:__lt____le____eq____ne__ __gt____ge__

2 特殊数值方法:__add____sub____mul____matmul__、<代码>__truediv__、__floordiv____mod____divmod____pow____lshift__ code>、__rshift____and____xor____or__ 及其 __r*__反射和 __i*__ 就地对应。

__ne__ 的错误实现 #1

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

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

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

class Correct:

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


class Incorrect:

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


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

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

不正确__ne__ 的实现 #2

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

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

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

class Correct:

    def __init__(self):
        self.state = False

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


class Incorrect:

    def __init__(self):
        self.state = False

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


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

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

理解比较运算

在数学中,二元关系 集合X上的R是一组有序对(xy >) X2R 中的语句 (x,y) 内容为“xR” -与y'相关,并用xRy表示。

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

  • R自反,对于 X 中的所有 xxRx >。
  • R非反身(也称为严格),适用于X中的所有x,而不是xRx
  • 对于所有情况,R对称 X 中的 xy,如果 xRyyRx
  • 对于所有情况,R反对称 xyX 中,如果 xRyyRxx y
  • R 对于所有人来说都是传递 X 中的 xyz,如果 xRyyRz 然后xRz
  • Rconnex(也称为总计),对于 XxRyxRy 中的所有 xy >yRx。
  • R等价关系 R 是自反、对称和传递的。
    例如,=。然而 ≠ 只是对称的。
  • R 是一个 顺序关系 R 是自反、反对称和传递的。
    例如,≤和≥。
  • R严格顺序关系 R 是非自反、反对称和传递时。
    例如,<和>。然而 ≠ 只是非自反的。

集合X上的两个二元关系RS的运算:

  • Rconverse 是二元关系 RT  = {(yx) | xRy}超过X
  • R是二元关系 ØR = {(xy) | 不是 xRy }超过X
  • R联合 S 是二元关系R ∪ S = {(x,  y) | xRyxSy} 超过 X

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

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

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

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

因此,要在 Python 中正确实现比较运算符 ==!=<>、< code><=、>=分别对应比较关系=、≠、<、>、≤、≥,以上数学性质和关系均应成立。

比较操作x运算符y调用其操作数之一的类的特殊比较方法__operator__

class X:

    def __operator__(self, other):
        # implementation

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

相等比较(==!=)的默认行为基于
关于对象的身份。因此,平等比较
具有相同身份的实例导致平等,并且平等
具有不同身份的实例的比较结果
不等式。这种默认行为的动机是希望
所有对象都应该是自反的(即x is y意味着x == y)。

默认顺序比较(<><=>= ) 未提供;
尝试引发TypeError。这种默认行为的动机
缺乏与平等类似的不变量。 [这是不正确的,因为 <=>= 是自反的,就像 ==<> 是非自反的,就像 !=.]


object 提供了特殊比较方法的默认实现,这些方法由其所有子类继承,如 Python 文档

object.__lt__(self, other)
object.__le__(自身,其他)
object.__eq__(self, other)
object.__ne__(self, other)
object.__gt__(自身,其他)
object.__ge__(self, other)

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

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

[…]

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

由于R = (RT)T,比较xRy相当于相反比较yRTx(在Python文档中非正式地命名为“reflected”)。因此,有两种方法可以计算比较运算 x 运算符 y 的结果:调用 x.__operator__(y) 或 y.__operatorT__(x)< /代码>。 Python 使用以下计算策略:

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

在 CPython 中 这是用 C 代码实现的,可以翻译成 Python 代码(== 的名称为 eq!= 的名称为 ne >、lt 表示 <gt 表示 >le 表示 < code><= 和 ge(对于 >=):

def eq(left, right):
    if type(left) != type(right) and isinstance(right, type(left)):
        result = right.__eq__(left)
        if result is NotImplemented:
            result = left.__eq__(right)
    else:
        result = left.__eq__(right)
        if result is NotImplemented:
            result = right.__eq__(left)
    if result is NotImplemented:
        result = left is right
    return result
def ne(left, right):
    if type(left) != type(right) and isinstance(right, type(left)):
        result = right.__ne__(left)
        if result is NotImplemented:
            result = left.__ne__(right)
    else:
        result = left.__ne__(right)
        if result is NotImplemented:
            result = right.__ne__(left)
    if result is NotImplemented:
        result = left is not right
    return result
def lt(left, right):
    if type(left) != type(right) and isinstance(right, type(left)):
        result = right.__gt__(left)
        if result is NotImplemented:
            result = left.__lt__(right)
    else:
        result = left.__lt__(right)
        if result is NotImplemented:
            result = right.__gt__(left)
    if result is NotImplemented:
        raise TypeError(
            f"'<' not supported between instances of '{type(left).__name__}' "
            f"and '{type(right).__name__}'"
        )
    return result
def gt(left, right):
    if type(left) != type(right) and isinstance(right, type(left)):
        result = right.__lt__(left)
        if result is NotImplemented:
            result = left.__gt__(right)
    else:
        result = left.__gt__(right)
        if result is NotImplemented:
            result = right.__lt__(left)
    if result is NotImplemented:
        raise TypeError(
            f"'>' not supported between instances of '{type(left).__name__}' "
            f"and '{type(right).__name__}'"
        )
    return result
def le(left, right):
    if type(left) != type(right) and isinstance(right, type(left)):
        result = right.__ge__(left)
        if result is NotImplemented:
            result = left.__le__(right)
    else:
        result = left.__le__(right)
        if result is NotImplemented:
            result = right.__ge__(left)
    if result is NotImplemented:
        raise TypeError(
            f"'<=' not supported between instances of '{type(left).__name__}' "
            f"and '{type(right).__name__}'"
        )
    return result
def ge(left, right):
    if type(left) != type(right) and isinstance(right, type(left)):
        result = right.__le__(left)
        if result is NotImplemented:
            result = left.__ge__(right)
    else:
        result = left.__ge__(right)
        if result is NotImplemented:
            result = right.__le__(left)
    if result is NotImplemented:
        raise TypeError(
            f"'>=' not supported between instances of '{type(left).__name__}' "
            f"and '{type(right).__name__}'"
        )
    return result

由于 R = Ø(ØR),比较xRy相当于比较 Ø(xØRy)。 ≠ 是= 的补码,因此默认情况下,特殊方法__ne__ 是根据支持的操作数的特殊方法__eq__ 实现的,而其他特殊比较方法则独立实现默认情况下(事实上 ≤ 是 < 和 = 的并集,≥ 是 > 和 = 的并集 令人惊讶的是没有考虑,这意味着目前特殊方法 __le__ 和 < code>__ge__ 应该由用户实现),如 Python 文档

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


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

def __eq__(self, other):
    return self is other or NotImplemented
def __ne__(self, other):
    result = self.__eq__(other)
    if result is not NotImplemented:
        return not result
    return NotImplemented
def __lt__(self, other):
    return NotImplemented
def __gt__(self, other):
    return NotImplemented
def __le__(self, other):
    return NotImplemented
def __ge__(self, other):
    return NotImplemented

所以默认情况下:

  • 比较操作x运算符y会引发异常TypeError,比较运算符==<除外/code> 和 != 如果操作数 x 和 <,则分别返回值 TrueFalse code>y 分别为相同和不同,否则值为 FalseTrue
  • 特殊比较方法调用 x.__operator__(y) 返回值 NotImplemented,特殊比较方法 __eq____ne__ 除外如果操作数 xy 分别相同,则它分别返回值 TrueFalse不相同,否则值为 NotImplemented

Correct implementation of __ne__

@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 implementation of __ne__ #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 implementation of __ne__ #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.state = False

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


class Incorrect:

    def __init__(self):
        self.state = False

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


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

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

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 of < and =, and ≥ is the union of > and =.

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

  • 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 compares 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 values True and False if the operands x and y are respectively identical and non-identical, and the values False and True otherwise;
  • 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 the values True and False if the operands x and y are respectively identical and non-identical, and the value NotImplemented otherwise.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文