Python,我应该基于__eq__实现__ne__()运算符吗?
简短回答:不要实现它,但如果必须,请使用==,而不是__eq__
在Python 3中,!=默认是==的否定,所以你甚至不需要写__ne__,文档也不再固执己见。
一般来说,对于仅 Python 3 的代码,除非您需要掩盖父实现,例如,否则不要编写任何代码。对于内置对象。
也就是说,记住Raymond Hettinger's comment:
__ne__ 方法自动从__eq__ 遵循,仅当
__ne__ 尚未在超类中定义。所以,如果你是
从内置继承,最好覆盖两者。
如果您需要您的代码在 Python 2 中工作,请遵循 Python 2 的建议,它可以在 Python 3 中正常工作。
在 Python 2 中,Python 本身不会自动实现对另一个的任何操作 - 因此,您应该根据 == 而不是 __eq__ 定义 __ne__。
例如。
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 的 documentation 表示:
比较运算符之间没有隐含的关系。这
x==y 的真实性并不意味着 x!=y 是错误的。因此,当
定义__eq__(),还应该定义__ne__(),这样
运算符将按预期运行。
这意味着如果我们根据__eq__ 的逆来定义__ne__,我们可以获得一致的行为。
文档的此部分已针对 Python 3: 进行了更新
默认情况下,__ne__() 委托给__eq__() 并反转结果
除非是NotImplemented。
在"what's new" section 中,我们看到这种行为发生了变化:
-
!= 现在返回与 == 相反的结果,除非 == 返回 NotImplemented。
为了实现__ne__,我们更喜欢使用==操作符而不是直接使用__eq__方法,这样如果子类的self.__eq__(other)返回NotImplemented的类型选中,Python 会适当检查other.__eq__(self) From the documentation:
NotImplemented 对象
这种类型只有一个值。有一个具有此值的对象。该对象通过内置名称访问
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__:
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
和,
>>> 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
上面的结果应该是False!
Python 3 源代码
__ne__ 的默认 CPython 实现位于 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__ 使用__eq__?
Python 3 在 C 级别的默认 __ne__ 实现细节使用 __eq__,因为更高级别的 == (PyObject_RichCompare) 效率较低 - 因此它还必须处理 NotImplemented。
如果__eq__ 被正确实现,那么== 的否定也是正确的——它允许我们避免__ne__ 中的低级实现细节。
使用== 允许我们将低级逻辑保留在一个位置,并避免在__ne__ 中寻址NotImplemented。
人们可能会错误地认为== 可能会返回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 是不正确的,因为它永远无法返回 NotImplemented(not NotImplemented 是 False),因此具有优先级的 __ne__ 方法永远不会退回到__ne__ 没有优先级的方法。
拥有__ne__ 永远不会返回NotImplemented 并不意味着它不正确。相反,我们使用NotImplemented 通过检查与== 的相等性来处理优先级。假设== 正确实现,我们就完成了。
not self == other 曾经是 __ne__ 方法的默认 Python 3 实现,但它是一个错误,并于 2015 年 1 月在 Python 3.4 中得到纠正,正如 ShadowRanger 所注意到的(参见问题 #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 中引入的,使得
冗余。所有相关的__ne__ 方法都被删除了,包括实现自己的检查的方法以及直接或通过== 委托给__eq__ 的方法——而== 是最常用的方法。
对称重要吗?
我们坚持不懈的批评者提供了一个病态的例子来说明在__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 级别的低级否定 - 它甚至更简单且高效(尽管程序员有责任确定它正确 em>)。
再次,不要在高级 Python 中编写低级逻辑。