__ne__是否应该作为__eq__的否定实现?
我有一个类,我想要重写 __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)
4 个回答
为了记录一下,一个标准的、可以在Python 2和Python 3之间通用的__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)
不同,这个方法在一些复杂的比较情况下不会干扰结果,比如当参与比较的类之一并不保证__ne__
的结果和not
在__eq__
上的结果相同的情况(例如在SQLAlchemy的ORM中,__eq__
和__ne__
都返回特殊的代理对象,而不是True
或False
,试图对__eq__
的结果使用not
会返回False
,而不是正确的代理对象)。 - 与
not self.__eq__(other)
不同,当self.__eq__
返回NotImplemented
时,这个方法会正确地委托给另一个实例的__ne__
(not self.__eq__(other)
会更错,因为NotImplemented
是“真”的,所以当__eq__
不知道如何进行比较时,__ne__
会返回False
,这意味着两个对象是相等的,而实际上询问的对象并不知道,这就意味着默认是不相等的)。
如果你的__eq__
没有使用NotImplemented
的返回值,这个方法是有效的(虽然有些多余),如果有时使用NotImplemented
,这个方法可以正确处理。而且Python版本检查意味着如果这个类是在Python 3中被import
的,__ne__
会保持未定义,这样Python的本地高效的__ne__
实现(上面提到的C版本)就可以接管。
为什么需要这个
Python的运算符重载规则
解释为什么要这样做而不是其他解决方案有点复杂。Python对运算符重载有一些通用规则,特别是比较运算符:
- (适用于所有运算符)当运行
LHS OP RHS
时,先尝试LHS.__op__(RHS)
,如果返回NotImplemented
,再尝试RHS.__rop__(LHS)
。例外:如果RHS
是LHS
类的子类,那么先测试RHS.__rop__(LHS)
。在比较运算符的情况下,__eq__
和__ne__
是它们自己的“反向运算符”(所以__ne__
的测试顺序是LHS.__ne__(RHS)
,然后RHS.__ne__(LHS)
,如果RHS
是LHS
的子类则顺序反转)。 - 除了“交换”运算符的概念外,运算符之间没有隐含关系。即使是同一个类的实例,
LHS.__eq__(RHS)
返回True
也不意味着LHS.__ne__(RHS)
返回False
(实际上,这些运算符甚至不需要返回布尔值;像SQLAlchemy这样的ORM故意不这样做,以允许更具表现力的查询语法)。在Python 3中,默认的__ne__
实现是这样工作的,但这并不是强制性的;你可以以不严格相反的方式重载__ne__
。
这如何应用于重载比较器
所以当你重载一个运算符时,你有两个任务:
- 如果你知道如何自己实现这个操作,就这样做,只使用你自己对如何进行比较的知识(绝不要隐式或显式地委托给操作的另一方;这样做可能会导致不正确和/或无限递归,具体取决于你怎么做)。
- 如果你不知道如何自己实现这个操作,总是返回
NotImplemented
,这样Python就可以委托给另一个操作数的实现。
not self.__eq__(other)
的问题
def __ne__(self, other):
return not self.__eq__(other)
从不委托给另一方(如果__eq__
正确返回NotImplemented
,这就是错误的)。当self.__eq__(other)
返回NotImplemented
(这是“真”的),你默默地返回False
,所以A() != something_A_knows_nothing_about
返回False
,而它应该检查something_A_knows_nothing_about
是否知道如何与A
的实例进行比较,如果不知道,它应该返回True
(因为如果两边都不知道如何比较对方,它们被认为是不相等的)。如果A.__eq__
实现不正确(在不识别另一方时返回False
而不是NotImplemented
),那么从A
的角度来看这是“正确的”,返回True
(因为A
认为它不相等,所以它不相等),但从something_A_knows_nothing_about
的角度来看可能是错误的,因为它甚至没有询问something_A_knows_nothing_about
;A() != something_A_knows_nothing_about
最终返回True
,但something_A_knows_nothing_about != A()
可能返回False
,或者其他任何返回值。
not self == other
的问题
def __ne__(self, other):
return not self == other
更微妙。对于99%的类来说,这个方法是正确的,包括所有__ne__
是__eq__
逻辑反转的类。但not self == other
违反了上面提到的两个规则,这意味着对于__ne__
不是__eq__
逻辑反转的类,结果再次是不对称的,因为其中一个操作数从未被询问是否可以实现__ne__
,即使另一个操作数不能。最简单的例子是一个奇怪的类,它对所有比较都返回False
,所以A() == Incomparable()
和A() != Incomparable()
都返回False
。如果A.__ne__
正确实现(在不知道如何进行比较时返回NotImplemented
),那么关系是对称的;A() != Incomparable()
和Incomparable() != A()
对结果达成一致(因为在前一种情况下,A.__ne__
返回NotImplemented
,然后Incomparable.__ne__
返回False
,而在后者中,Incomparable.__ne__
直接返回False
)。但是当A.__ne__
被实现为return not self == other
时,A() != Incomparable()
返回True
(因为A.__eq__
返回,而不是NotImplemented
,然后Incomparable.__eq__
返回False
,而A.__ne__
将其反转为True
),而Incomparable() != A()
返回False
。
你可以在这里看到这个例子的实际效果。
显然,一个对__eq__
和__ne__
都总是返回False
的类有点奇怪。但如前所述,__eq__
和__ne__
甚至不需要返回True
/False
;SQLAlchemy ORM中的类有比较器,返回一个特殊的代理对象用于查询构建,而不是True
/False
(在布尔上下文中它们是“真”的,但它们并不应该在这样的上下文中被评估)。
通过未能正确重载__ne__
,你将会破坏这类对象,因为代码:
results = session.query(MyTable).filter(MyTable.fieldname != MyClassWithBadNE())
将会正常工作(假设SQLAlchemy知道如何将MyClassWithBadNE
插入到SQL字符串中;这可以通过类型适配器完成,而MyClassWithBadNE
根本不需要配合),将预期的代理对象传递给filter
,而:
results = session.query(MyTable).filter(MyClassWithBadNE() != MyTable.fieldname)
将最终传递给filter
一个普通的False
,因为self == other
返回一个代理对象,而not self == other
只是将这个“真”的代理对象转换为False
。希望filter
在处理像False
这样的无效参数时会抛出异常。虽然我相信很多人会争辩说MyTable.fieldname
应该始终在比较的左侧,但事实是没有程序上的理由在一般情况下强制执行这一点,而一个正确的通用__ne__
将无论如何都能工作,而return not self == other
只在一种排列中有效。
在Python中,我应该根据
__eq__
来实现__ne__()
运算符吗?
简短回答:不要实现它,但如果必须,使用==
,而不是__eq__
在Python 3中,!=
默认是==
的反义词,所以你甚至不需要写__ne__
,文档也不再强烈建议写一个。
一般来说,对于仅限Python 3的代码,除非你需要覆盖父类的实现,比如对于内置对象,否则不要写这个。
也就是说,记住Raymond Hettinger的评论:
只有当
__ne__
在超类中没有定义时,__ne__
方法才会自动跟随__eq__
。所以,如果你是从内置类继承的,最好同时覆盖这两个。
如果你需要你的代码在Python 2中工作,按照Python 2的建议去做,它在Python 3中也能正常工作。
在Python 2中,Python本身不会自动根据其他操作来实现任何操作,因此你应该根据==
来定义__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)`
请看下面的证明:
- 根据
__eq__
实现__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)
返回NotImplemented
,Python会适当地检查other.__eq__(self)
。根据文档:
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
请记住,这些是用__eq__
实现的__ne__
- 虽然这是预期的行为,但实现是不正确的:
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源代码
默认的CPython实现__ne__
在typeobject.c
中的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的默认__ne__
实现细节在C层面上使用__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的实现
not self == other
的__ne__
方法是不正确的,因为它永远不会返回NotImplemented
(not NotImplemented
是False
),因此优先级较高的__ne__
方法永远无法回退到优先级较低的__ne__
方法。
让__ne__
永远不返回NotImplemented
并不意味着它不正确。相反,我们通过与==
的等式检查来处理NotImplemented
的优先级。假设==
实现正确,我们就完成了。
not self == other
曾经是Python 3中__ne__
方法的默认实现,但这是一个错误,并在2015年1月的Python 3.4中修正,正如ShadowRanger所注意到的(见问题#21408)。
好吧,让我们解释一下。
如前所述,Python 3默认处理__ne__
时,首先检查self.__eq__(other)
是否返回NotImplemented
(一个单例) - 如果是,应该用is
检查并返回,否则应该返回反义。以下是作为类混入的逻辑:
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层面的低级反义 - 这甚至更简单和高效(尽管程序员负责确保它是正确的)。
再次强调,不要在高级Python中编写低级逻辑。
没问题,这样做完全可以。实际上,文档建议你在定义 __eq__
的时候,也要定义 __ne__
:
比较操作符之间没有隐含的关系。也就是说,
x==y
为真,并不意味着x!=y
是假。因此,在定义__eq__()
的时候,也应该定义__ne__()
,这样这些操作符的行为才会符合预期。
在很多情况下(比如这个例子),你只需要简单地把 __eq__
的结果取反就可以了,但并不总是如此。