我有一个课我要覆盖__eq__()
运算符。似乎我应该覆盖__ne__()
运算符,但基于__ne__
实现__eq__
是否有意义呢?
class A:
def __eq__(self, other):
return self.value == other.value
def __ne__(self, other):
return not self.__eq__(other)
或者,Python使用这些运算符的方式是否缺少某些内容,这使得这不是一个好主意?
答案 0 :(得分:93)
Python,我应该基于
__ne__()
实现__eq__
运算符吗?
==
代替__eq__
在Python 3中,默认情况下!=
是==
的否定,因此您甚至不需要编写__ne__
,并且文档不再适用于编写文档。
一般来说,对于仅限Python 3的代码,除非您需要掩盖父实现,例如,否则不要编写代码。对于内置对象。
也就是说,请记住Raymond Hettinger's comment:
__ne__
方法仅在__eq__
时自动跟随__ne__
尚未在超类中定义。所以,如果你是 从内置继承,最好覆盖它们。
如果您需要在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)`
见证明
__ne__()
和__eq__
运算符
__ne__
在下面的演示中提供了错误的行为。
Python 2的documentation说:
比较运营商之间没有隐含的关系。该
x==y
的真相并不意味着x!=y
是假的。因此,何时 定义__eq__()
,还应定义__ne__()
以便{。}} 运营商将按预期行事。
这意味着如果我们根据__ne__
的倒数定义__eq__
,我们就可以获得一致的行为。
本文档的这一部分已针对Python 3:
进行了更新默认情况下,
__ne__()
委托给__eq__()
并反转结果 除非它是NotImplemented
。
在"what's new" section中,我们看到此行为已更改:
!=
现在返回==
的反面,除非==
返回NotImplemented
。
为了实施__ne__
,我们更倾向于使用==
运算符,而不是直接使用__eq__
方法,以便在self.__eq__(other)
时使用NotImplemented
子类为选中的类型返回other.__eq__(self)
,Python将适当地检查NotImplemented
From the documentation:
NotImplemented
对象此类型具有单个值。有一个具有此值的对象。可以通过内置名称访问此对象
other
。数值方法和丰富的比较方法可能会返回 如果它们没有实现操作数的操作,则为该值 提供。 (解释器然后将尝试反射操作,或 其他一些后备,取决于运算符。)它的真值是 真。
当给定一个富比较运算符时,如果它们不是同一类型,Python会检查other
是否为子类型,如果它定义了该运算符,则使用<
首先是方法(与<=
,>=
,>
和NotImplemented
相反)。如果返回==
,则然后使用相反的方法。 (它不检查两次相同的方法。)使用__ne__
运算符可以实现此逻辑。
从语义上讲,您应该在检查相等性方面实现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
,因为您的类的用户希望以下函数对于A的所有实例都是等效的。
__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
:
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
这些在Python 3下测试的实例也能正常工作:
__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
和
__ne__
__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
以上结果应为__ne__
!
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);
}
}
的默认CPython实现位于typeobject.c
in object_richcompare
:
__ne__
我们在这里看到
__eq__
使用__ne__
? Python 3的C级默认__eq__
实施详细信息使用==
,因为较高级别NotImplemented
(PyObject_RichCompare)效率较低 - 因此它还必须处理__eq__
。
如果==
被正确实施,那么__ne__
的否定也是正确的 - 它允许我们在==
中避免低级别的实施细节。
使用NotImplemented
可以让我们将低级别逻辑保留在一个位置,避免<{em>} __ne__
中的==
。
有人可能错误地认为NotImplemented
可能会返回__eq__
。
它实际上使用与class Foo:
def __ne__(self, other):
return NotImplemented
__eq__ = __ne__
f = Foo()
f2 = Foo()
的默认实现相同的逻辑,用于检查身份(请参阅下面的do_richcompare和我们的证据)
>>> 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
当您考虑not self == other
在Python中执行逻辑时,这是有道理的,否则将在C级别处理。
另一位回答者写道:
Aaron Hall对
__ne__
方法的实施NotImplemented
不正确,因为它永远不会返回not NotImplemented
(False
是__ne__
),因此{{1具有优先级的方法永远不会回退到没有优先级的__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__
methods in this patch关闭Issue 21408和__ne__
methods in the follow-on cleanup removed here 多余的。已删除所有相关的__ne__
方法,包括实施自己的检查以及直接或__eq__
委托给==
的方法 - 而==
是最常见的方法这样做。
对于Python 2兼容代码,请使用==
来实现__ne__
。它更多:
仅在Python 3中,使用C级别的低级否定 - 它甚至更多简单且高效(尽管程序员负责确定它正确)。
同样,不在高级Python中编写低级逻辑。
答案 1 :(得分:47)
是的,这完全没问题。事实上,the documentation会促使您在定义__ne__
时定义__eq__
:
没有隐含的关系 在比较运算符中。该
x==y
的真相并不意味着x!=y
是假的。因此,在定义时__eq__()
,还应该定义__ne__()
,以便运算符按预期运行。
在很多情况下(比如这个),它就像否定__eq__
的结果一样简单,但并非总是如此。
答案 2 :(得分:7)
只是为了记录,规范正确并且交叉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)
不同,在涉及比较的一些烦人/复杂案件中不会干扰,其中一个类别并不意味着__ne__
的结果与not
__eq__
的结果(例如SQLAlchemy的ORM,其中__eq__
和__ne__
都返回特殊代理对象,而不是True
或{{ 1}},并尝试False
not
的结果将返回__eq__
,而不是正确的代理对象)。 False
不同,当not self.__eq__(other)
返回__ne__
时,这会正确委托给其他实例的self.__eq__
(NotImplemented
会出错,因为{{ 1}}是真实的,所以当not self.__eq__(other)
不知道如何进行比较时,NotImplemented
会返回__eq__
,这意味着这两个对象实际上是唯一的被问到的对象不知道,这意味着默认不等于)如果您的__ne__
没有使用False
返回,则此功能(无意义的开销),如果有时使用__eq__
,则会正确处理。 Python版本检查意味着如果类是NotImplemented
- 在Python 3中编辑,则NotImplemented
未定义,允许Python的本机高效回退__ne__
implementation (a C version of the above)接管。
为什么你这样做而不是其他解决方案的解释有些神秘。 Python有一些关于重载运算符的一般规则,特别是比较运算符:
import
时,请尝试__ne__
,如果返回LHS OP RHS
,请尝试LHS.__op__(RHS)
。例外:如果NotImplemented
是RHS.__rop__(LHS)
类的子类,则先测试RHS
。对于比较运算符,LHS
和RHS.__rop__(LHS)
是他们自己的&#34; rop&#34;因此__eq__
的测试顺序为__ne__
,然后{ {1}},如果__ne__
是LHS.__ne__(RHS)
类的子类,则撤消RHS.__ne__(LHS)
返回RHS
并不意味着LHS
返回LHS.__eq__(RHS)
(事实上,运算符甚至不需要返回布尔值;像SQLAlchemy这样的ORM故意不这样做,允许更具表现力的查询语法)。从Python 3开始,默认的True
实现就是这样的,但它不是契约性的;您可以以LHS.__ne__(RHS)
的严格对立方式覆盖False
。因此,当您重载运算符时,您有两个作业:
__ne__
,因此Python可以委托给另一个操作数&#39 ; s实施__ne__
__eq__
从不委托给另一方(如果NotImplemented
正确返回not self.__eq__(other)
,则不正确)。当def __ne__(self, other):
return not self.__eq__(other)
返回__eq__
(&#34;真实&#34;)时,您会默默地返回NotImplemented
,因此self.__eq__(other)
会返回NotImplemented
,检查了False
是否知道如何与A() != something_A_knows_nothing_about
的实例进行比较,如果没有,则应该返回False
(因为如果双方都不知道如何与另外,他们被认为彼此不相等)。如果something_A_knows_nothing_about
未正确实施(当它不识别另一方时,则返回A
而不是True
),那么这是&#34;正确的&#34;从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()
更微妙。 99%的课程都是正确的,包括False
是not self == other
的逻辑反转的所有课程。但def __ne__(self, other):
return not self == other
违反了上述两条规则,这意味着对于__ne__
不是__eq__
的逻辑反转的类,结果是一次再次非反身,因为其中一个操作数永远不会被问及是否可以实现not self == other
,即使其他操作数不能。最简单的示例是一个weirdo类,它为所有比较返回__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
您可以在行动here中看到此示例。
显然,一个总是为Incomparable() != A()
和False.
返回False
的类有点奇怪。但如前所述,__eq__
和__ne__
甚至不需要返回__eq__
/ __ne__
; SQLAlchemy ORM具有带有比较器的类,它们返回一个用于查询构建的特殊代理对象,而不是True
/ False
(如果在布尔值中进行评估,它们就是&#34; truthy&#34;上下文,但他们永远不应该在这样的背景下进行评估。)
由于未能正确地重载True
,将破坏该类的类,如代码:
False
将起作用(假设SQLAlchemy知道如何将__ne__
插入到SQL字符串中;这可以通过类型适配器来完成,而 results = session.query(MyTable).filter(MyTable.fieldname != MyClassWithBadNE())
根本不需要合作),将预期的代理对象传递给MyClassWithBadNE
,同时:
MyClassWithBadNE
将最终传递filter
一个普通 results = session.query(MyTable).filter(MyClassWithBadNE() != MyTable.fieldname)
,因为filter
会返回一个代理对象,False
只会将真实的代理对象转换为self == other
。希望not self == other
在处理False
之类的无效参数时抛出异常。虽然我确信很多人会认为filter
应该始终位于比较的左侧,但事实仍然是没有程序化的理由来强制执行此操作。大小写,正确的通用False
将以任何方式工作,而MyTable.fieldname
仅适用于一种方式。
答案 3 :(得分:2)
ShadowRanger对__ne__
方法的实现是正确的(在某种意义上它的行为与默认的Python 3实现完全相同):
def __ne__(self, other):
result = self.__eq__(other)
if result is not NotImplemented:
return not result
return NotImplemented
Aaron Hall对not self == other
方法的实施__ne__
不正确,因为它永远不会返回NotImplemented
(not NotImplemented
是False
),因此{{1具有优先级的方法永远不会回退到没有优先级的__ne__
方法。 __ne__
曾经是not self == other
方法的默认Python 3实现,但它是一个错误,并且在2015年1月的Python 3.4中得到了纠正,正如ShadowRanger注意到的那样(请参阅issue #21408)。 / p>
用于Python 3的 Python语言参考在chapter III Data model中说明:
__ne__
object.__lt__(self, other)
object.__le__(self, other)
object.__eq__(self, other)
object.__ne__(self, other)
object.__gt__(self, other)
这些是所谓的“丰富比较”方法。通信 运算符符号和方法名称之间如下:
object.__ge__(self, other)
调用x<y
,x.__lt__(y)
来电x<=y
,x.__le__(y)
来电x==y
,x.__eq__(y)
来电x!=y
,x.__ne__(y)
来电x>y
和x.__gt__(y)
致电x>=y
。丰富的比较方法可能会返回单例
x.__ge__(y)
if 它没有实现给定参数对的操作。这些方法没有交换参数版本(可以使用 当左参数不支持操作但右边 论证确实);相反,
NotImplemented
和__lt__()
是彼此的 反射,__gt__()
和__le__()
是彼此的反映,和__ge__()
和__eq__()
是他们自己的反映。如果是操作数 是不同类型的,右操作数的类型是直接或 左操作数类型的间接子类,反射方法 右操作数具有优先级,否则为左操作数的方法 优先考虑。不考虑虚拟子类化。
将其翻译成Python代码(__ne__()
使用operator_eq
,==
使用operator_ne
,!=
使用operator_lt
,{{1} {}为<
,operator_gt
为>
,operator_le
为<=
):
operator_ge
文档补充说:
默认情况下,
>=
委托给def operator_eq(left, right): if type(left) != type(right) and isinstance(right, type(left)): result = right.__eq__(left) if result is NotImplemented: result = left.__eq__(right) else: result = left.__eq__(right) if result is NotImplemented: result = right.__eq__(left) if result is NotImplemented: result = left is right return result def operator_ne(left, right): if type(left) != type(right) and isinstance(right, type(left)): result = right.__ne__(left) if result is NotImplemented: result = left.__ne__(right) else: result = left.__ne__(right) if result is NotImplemented: result = right.__ne__(left) if result is NotImplemented: result = left is not right return result def operator_lt(left, right): if type(left) != type(right) and isinstance(right, type(left)): result = right.__gt__(left) if result is NotImplemented: result = left.__lt__(right) else: result = left.__lt__(right) if result is NotImplemented: result = right.__gt__(left) if result is NotImplemented: raise TypeError(f"'<' not supported between instances of '{type(left).__name__}' and '{type(right).__name__}'") return result def operator_gt(left, right): if type(left) != type(right) and isinstance(right, type(left)): result = right.__lt__(left) if result is NotImplemented: result = left.__gt__(right) else: result = left.__gt__(right) if result is NotImplemented: result = right.__lt__(left) if result is NotImplemented: raise TypeError(f"'>' not supported between instances of '{type(left).__name__}' and '{type(right).__name__}'") return result def operator_le(left, right): if type(left) != type(right) and isinstance(right, type(left)): result = right.__ge__(left) if result is NotImplemented: result = left.__le__(right) else: result = left.__le__(right) if result is NotImplemented: result = right.__ge__(left) if result is NotImplemented: raise TypeError(f"'<=' not supported between instances of '{type(left).__name__}' and '{type(right).__name__}'") return result def operator_ge(left, right): if type(left) != type(right) and isinstance(right, type(left)): result = right.__le__(left) if result is NotImplemented: result = left.__ge__(right) else: result = left.__ge__(right) if result is NotImplemented: result = right.__le__(left) if result is NotImplemented: raise TypeError(f"'>=' not supported between instances of '{type(left).__name__}' and '{type(right).__name__}'") return result
并反转结果 除非是__ne__()
。没有其他暗示 比较运算符之间的关系,例如,事实__eq__()
并不意味着NotImplemented
。
比较方法的默认实现((x<y or x==y)
,x<=y
,__eq__
,__ne__
,__lt__
和__gt__
)因此可以是由下式给出:
__le__
所以这是__ge__
方法的正确实现。并且它并不总是返回def __eq__(self, other):
return NotImplemented
def __ne__(self, other):
result = self.__eq__(other)
if result is not NotImplemented:
return not result
return NotImplemented
def __lt__(self, other):
return NotImplemented
def __gt__(self, other):
return NotImplemented
def __le__(self, other):
return NotImplemented
def __ge__(self, other):
return NotImplemented
方法的倒数,因为当__ne__
方法返回__eq__
时,其倒数__eq__
为NotImplemented
(因为{{1} }} not NotImplemented
}而不是所需的False
。
bool(NotImplemented)
正如Aaron Hall所示,True
不是NotImplemented
方法的默认实现。 但也不是__ne__
。下面通过比较两种情况下默认实现的行为与not self.__eq__(other)
实现的行为来演示后者:
__ne__
方法返回not self == other
; not self == other
方法返回的值不同于__eq__
。让我们看看当NotImplemented
方法使用默认实现并且__eq__
方法返回NotImplemented
时会发生什么:
A.__ne__
A.__eq__
来电NotImplemented
。class A:
pass
class B:
def __ne__(self, other):
return "B.__ne__"
assert (A() != B()) == "B.__ne__"
来电!=
。A.__ne__
返回A.__ne__
。A.__eq__
来电A.__eq__
。NotImplemented
返回!=
。这表明当B.__ne__
方法返回B.__ne__
时,"B.__ne__"
方法会回退到A.__eq__
方法。
现在让我们看看当NotImplemented
方法使用默认实现并且A.__ne__
方法返回的值不同于B.__ne__
时会发生什么:
A.__ne__
A.__eq__
来电NotImplemented
。class A:
def __eq__(self, other):
return True
class B:
def __ne__(self, other):
return "B.__ne__"
assert (A() != B()) is False
来电!=
。A.__ne__
返回A.__ne__
。A.__eq__
返回A.__eq__
,即True
。这表明在这种情况下,!=
方法返回not True
方法的反转。因此,False
方法的行为与文档中的广告相似。
使用上面给出的正确实现覆盖A.__ne__
方法的默认实现会产生相同的结果。
A.__eq__
实施让我们看看当使用__ne__
实现覆盖A.__ne__
方法的默认实现并且not self == other
方法返回A.__ne__
时会发生什么:
not self == other
A.__eq__
来电NotImplemented
。class A:
def __ne__(self, other):
return not self == other
class B:
def __ne__(self, other):
return "B.__ne__"
assert (A() != B()) is True
来电!=
。A.__ne__
来电A.__ne__
。==
返回==
。A.__eq__
来电A.__eq__
。NotImplemented
返回==
。B.__eq__
返回B.__eq__
,即NotImplemented
。==
返回A() is B()
,即False
。 A.__ne__
方法的默认实现返回not False
,而不是True
。
现在让我们看看在使用__ne__
实现覆盖"B.__ne__"
方法的默认实现时会发生什么,而True
方法返回的值与A.__ne__
不同:
not self == other
A.__eq__
来电NotImplemented
。class A:
def __eq__(self, other):
return True
def __ne__(self, other):
return not self == other
class B:
def __ne__(self, other):
return "B.__ne__"
assert (A() != B()) is False
来电!=
。A.__ne__
来电A.__ne__
。==
返回==
。A.__eq__
返回A.__eq__
,即True
。在这种情况下,A.__ne__
方法的默认实现也返回not True
。
由于当False
方法返回__ne__
时,此实现无法复制False
方法的默认实现的行为,因此它不正确。 < / p>
答案 4 :(得分:-1)
如果所有__eq__
,__ne__
,__lt__
,__ge__
,__le__
和__gt__
对该课程有意义,那么实现__cmp__
。否则,就像你正在做的那样,因为Daniel DiPaolo说道(当我测试它而不是查找它时);)