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

2023-12-04

我有一堂课我想覆盖__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)

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

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

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

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

也就是说,请记住雷蒙德·赫廷格的评论:

The __ne__方法自动遵循__eq__除非__ne__尚未在超类中定义。所以,如果你是 从内置函数继承,最好覆盖两者。

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

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

看看证明

  • 实施__ne__()运算符基于__eq__ and
  • 不执行__ne__完全在Python 2中

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

长答案

The 文档对于 Python 2 来说:

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

这意味着如果我们定义__ne__就逆而言__eq__,我们可以获得一致的行为。

文档的这一部分已更新为Python 3:

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

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

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

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

The NotImplemented object

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

当给定一个丰富的比较运算符时,如果它们不是同一类型,Python 会检查是否other是一个子类型,如果它定义了该运算符,则它使用other首先是 的方法(与<, <=, >= and >). If NotImplemented被返回,then它使用相反的方法。 (它确实not检查相同的方法两次。)使用==运算符允许发生此逻辑。


期望

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

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

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

首先是设置:

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

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

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

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

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

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

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

实例化非等效实例:

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

预期行为:

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

这些实例有__ne__实施与==:

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

这些实例在 Python 3 下测试也可以正常工作:

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

请记住,这些有__ne__实施与__eq__- 虽然这是预期的行为,但实现是不正确的:

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

意外行为:

请注意,此比较与上面的比较相矛盾(not wrong1 == wrong2).

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

and,

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

不要跳过__ne__在Python 2中

证据表明您不应该跳过实施__ne__在 Python 2 中,查看这些等效对象:

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

上面的结果应该是False!

Python 3 源代码

默认的 CPython 实现__ne__ 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;

但默认的__ne__ uses __eq__?

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

If __eq__被正确实现,则否定==也是正确的 - 它使我们能够避免我们的低级实现细节__ne__.

Using ==允许我们保持低级逻辑one地点,以及avoid寻址NotImplemented in __ne__.

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

它实际上使用与默认实现相同的逻辑__eq__,它检查身份(参见做丰富比较以及我们的证据如下)

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

f = Foo()
f2 = Foo()

以及比较:

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

表现

不要相信我的话,让我们看看什么更高效:

class CLevel:
    "Use default logic programmed in C"

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

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

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

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

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

我认为这些性能数字不言而喻:

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

当你考虑到这一点时,这是有道理的low_level_python在 Python 中执行本来可以在 C 级别处理的逻辑。

对一些批评者的回应

另一位回答者写道:

Aaron Hall 的实施not self == other of the __ne__方法不正确,因为它永远无法返回NotImplemented (not NotImplemented is False),因此__ne__具有优先级的方法永远不能依赖于__ne__没有优先级的方法。

Having __ne__永远不会回来NotImplemented不会使它不正确。相反,我们处理优先级NotImplemented通过检查是否等于==。假设==正确实施后,我们就完成了。

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

好吧,让我们解释一下。

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

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

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

  • the __ne__这个补丁中的方法关闭问题 21408 and
  • the __ne__此处删除了后续清理中的方法

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

对称性重要吗?

我们坚持不懈的批评家提供了一个病态的例子来说明处理的理由NotImplemented in __ne__,对称性高于一切。让我们用一个明显的例子来论证这一论点:

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

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

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

所以,按照这个逻辑,为了保持对称性,我们需要写出复杂的__ne__,无论 Python 版本如何。

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

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

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

显然,我们不应该介意这些实例既相等又不相等。

我认为对称性并不重要,重要的是合理代码的假设和遵循文档的建议。

然而,如果 A 有一个明智的实现__eq__,那么我们仍然可以遵循我的方向,并且我们仍然具有对称性:

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

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

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

结论

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

  • correct
  • simple
  • 表演者

仅在 Python 3 中,在 C 级别上使用低级否定 - 它是偶数more简单且高性能(尽管程序员有责任确定它是correct).

再次,做not用高级 Python 编写低级逻辑。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

__ne__ 应该作为 __eq__ 的否定来实现吗? 的相关文章

随机推荐