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

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

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

还是有什么,我错过了Python使用这些运算符的方式,这不是一个好主意?

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

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

在很多情况下(比如这个),它会像__eq__的结果一样简单,但并不总是如此。

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

简答:不用,用==代替__eq__

在Python 3中, !=是默认情况下的==的否定,因此甚至不需要编写__ne__ ,而且在编写文档时不再需要文档。 但请记住Raymond Hettinger的评论 :

只有__ne__在超类中没有定义, __ne__方法才会自动从__eq__开始。 所以,如果你从一个内buildinheritance,最好重写这两个。

另外,如果你需要你的代码在Python 2中工作,按照Python 2的build议,它将在Python 3中工作得很好。

在Python 2中,根据==定义__ne__而不是__eq__ 。 例如

 class A(object): def __eq__(self, other): return self.value == other.value def __ne__(self, other): return not self == other # NOT `return not self.__eq__(other)` 

见certificate

  • 基于__eq__和。实现__ne__()运算符
  • 根本__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)为checkedtypes返回NotImplemented ,Python将适当地检查other.__eq__(self) 文档 :

NotImplemented对象

这种types有一个单一的值。 有这个值的单个对象。 该对象通过内置名称NotImplemented 。 数字方法和丰富的比较方法可能会返回这个值,如果他们没有实现提供的操作数的操作。 (解释者然后将根据操作员尝试reflection的操作或其他一些后备操作。)它的真值是正确的。

当给定一个丰富的比较运算符时,如果它们不是相同的types,则Python检查other是否是子types,如果它具有该运算符定义的,则首先使用other的方法(对于<<=>=> )。 如果返回NotImplemented使用相反的方法。 (它不检查相同的方法两次。)使用==运算符允许这个逻辑发生。


期望

从语义__ne__ ,你应该在检查平等方面实现__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 

也就是说,上述两个函数都应该总是返回相同的结果。 但是这取决于程序员,因为Python本身不会自动执行任何操作。

基于__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__, eg 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下进行testing的这些实例也能正常工作:

 >>> 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 >>> assert not wrong2 == wrong1 

意外行为:

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

注意:虽然上面每一个的第二个断言是等价的,因此在逻辑上是多余的,但是我将它们包括来certificate当一个是另一个的子类时,顺序并不重要。

不要跳过Python 2中的__ne__

为certificate您不应该在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 

以上结果应该是False

Python 3源

CPython实现在typeobject.c

  case Py_NE: /* By default, __ne__() delegates to __eq__() and inverts the result, unless the latter returns NotImplemented. */ if (self->ob_type->tp_richcompare == NULL) { res = Py_NotImplemented; Py_INCREF(res); break; } res = (*self->ob_type->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; 

只是为了logging,规范正确和跨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) ,不会干涉一些烦人的/复杂的情况,涉及一个实例是另一个实例的子类的实例之间的比较。 如果你的__eq__没有使用NotImplemented返回值,这个工作(没有意义的开销),如果它有时候使用NotImplemented ,它会正确处理它。 Python版本检查意味着如果在Python 3中import类,则__ne__被定义,从而允许Python的本地高效回退__ne__实现(上述的C版本)来接pipe。

如果所有__ne____lt____ge__ __ne____lt____ge__ __le____gt__对于类是有意义的,那么只需实现__cmp__ 。 否则,就像你做的那样,因为丹尼尔·迪保罗(Daniel DiPaolo)说的(当我testing它时,而不是查看它)