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__
。更多的是:
仅在 Python 3 中,在 C 级别上使用低级否定 - 它是偶数more简单且高性能(尽管程序员有责任确定它是correct).
再次,做not用高级 Python 编写低级逻辑。