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它时,而不是查看它)