我有一个类,我想覆盖__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使用这些运算符的方式是否缺少某些东西,这不是一个好主意?
是的,那很好。事实上,当你定义the documentation时,__ne__
敦促你定义__eq__
:
比较运算符之间没有隐含的关系。
x==y
的真相并不意味着x!=y
是假的。因此,在定义__eq__()
时,还应该定义__ne__()
,以便运算符的行为符合预期。
在很多情况下(比如这个),它就像否定__eq__
的结果一样简单,但并非总是如此。
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
,Python将适当地检查other.__eq__(self)
From the documentation:
NotImplemented
object此类型具有单个值。有一个具有此值的对象。可以通过内置名称
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
也就是说,上述两个函数应始终返回相同的结果。但这取决于程序员。
__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
和,
>>> assert wrong2 != wrong1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError
__ne__
in Python 2有关您不应该在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
!
__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 (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);
}
}
我们在这里看到
__ne__
使用__eq__
?Python 3在C级别的默认__ne__
实现细节使用__eq__
,因为较高级别的==
(PyObject_RichCompare)效率较低 - 因此它还必须处理NotImplemented
。
如果__eq__
被正确实现,那么==
的否定也是正确的 - 它允许我们避免在我们的__ne__
中的低级实现细节。
使用==
允许我们将低级逻辑保持在一个位置,并避免在NotImplemented
中寻址__ne__
。
人们可能错误地认为==
可能会返回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
当您考虑not self == other
在Python中执行逻辑时,这是有道理的,否则将在C级别处理。
另一位回答者写道:
亚伦霍尔执行
__ne__
方法的NotImplemented
是不正确的,因为它永远不会返回not NotImplemented
(False
是__ne__
),因此具有优先权的__ne__
方法永远不会回到没有优先权的__ne__
方法。
让NotImplemented
永远不会返回NotImplemented
不会使它不正确。相反,我们通过检查与==
的平等来处理==
的优先级。假设正确实施not self == other
,我们就完成了。
__ne__
曾经是__ne__
方法的默认Python 3实现,但它是一个错误,它在2015年1月的Python 3.4中得到了纠正,正如ShadowRanger注意到的那样(参见问题#21408)。
好吧,我们来解释一下。
如前所述,默认情况下Python 3通过首先检查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
检查并返回,如果是这样,否则它应该返回逆。这是作为类mixin编写的逻辑:
the __ne__
methods in this patch
这对于C级Python API的正确性是必要的,并且它是在Python 3中引入的
多余的。删除了所有相关的__eq__
方法,包括实施自己的检查以及直接或通过==
委托给==
的方法 - 而==
是最常见的方法。
对于Python 2兼容代码,请使用__ne__
实现__ne__
。它更多:
仅在Python 3中,使用C级别的低级否定 - 它更加简单和高效(尽管程序员负责确定它是正确的)。
同样,不要在高级Python中编写低级逻辑。
只是为了记录,规范正确和交叉Py2 / Py3便携式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__
上的__eq__
的结果相同(例如SQLAlchemy的ORM,其中__ne__
和True
返回特殊代理对象,而不是False
或not
,并尝试__eq__
False
的结果将返回not self.__eq__(other)
,而不是正确的代理对象)。__ne__
不同,当self.__eq__
返回NotImplemented
时,这正确地委托给另一个实例的not self.__eq__(other)
(NotImplemented
将是额外的错误,因为__eq__
是真正的,所以当__ne__
不知道如何进行比较时,False
将返回__eq__
,暗示两个对象是相等的,实际上只有被问到的对象不知道,这意味着默认不相等)如果你的NotImplemented
不使用NotImplemented
返回,这是有效的(无意义的开销),如果有时使用import
,这将正确处理它。 Python版本检查意味着如果该类在Python 3中是__ne__
-ed,那么__ne__
implementation (a C version of the above)将保持未定义状态,从而允许Python的本机高效后备LHS OP RHS
接管。
你为什么这样做而不是其他解决方案的解释有些神秘。 Python有一些关于重载运算符的一般规则,特别是比较运算符:
LHS.__op__(RHS)
时,请尝试NotImplemented
,如果返回RHS.__rop__(LHS)
,请尝试使用RHS
。例外:如果LHS
是RHS.__rop__(LHS)
类的子类,那么首先测试__eq__
。在比较运算符的情况下,__ne__
和__ne__
是他们自己的“rop”(所以LHS.__ne__(RHS)
的测试顺序是RHS.__ne__(LHS)
,然后RHS
,如果LHS
是LHS.__eq__(RHS)
类的子类,则反转)True
返回LHS.__ne__(RHS)
并不意味着False
返回__ne__
(事实上,运算符甚至不需要返回布尔值;像SQLAlchemy那样的ORM故意不这样做,允许更具表现力的查询语法)。从Python 3开始,默认的__ne__
实现就是这样,但它不是契约性的;你可以用与__eq__
不严格对立的方式覆盖NotImplemented
。因此,当您重载运算符时,您有两个作业:
not self.__eq__(other)
,以便Python可以委托给其他操作数的实现def __ne__(self, other):
return not self.__eq__(other)
的问题__eq__
从不代表另一方(如果NotImplemented
正确返回self.__eq__(other)
,则不正确)。当NotImplemented
返回False
(这是“truthy”)时,你默默地返回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
的问题__ne__
更微妙。 99%的班级都是正确的,包括__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.
返回here
你可以在行动False
中看到这个例子。
显然,一个总是为__eq__
和__ne__
返回__eq__
的类有点奇怪。但如前所述,__ne__
和True
甚至不需要返回False
/ True
; SQLAlchemy ORM具有带有比较器的类,它们为查询构建返回一个特殊的代理对象,而不是False
/ __ne__
(如果在布尔上下文中进行评估,它们是“真正的”,但它们绝不应该在这样的上下文中进行评估)。
如果没有正确地重载 results = session.query(MyTable).filter(MyTable.fieldname != MyClassWithBadNE())
,你将破坏那种类,如代码:
MyClassWithBadNE
将工作(假设SQLAlchemy知道如何将MyClassWithBadNE
插入到SQL字符串中;这可以通过类型适配器完成,而filter
根本不需要合作),将预期的代理对象传递给 results = session.query(MyTable).filter(MyClassWithBadNE() != MyTable.fieldname)
,同时:
filter
最终将False
传递给一个简单的self == other
,因为not self == other
返回一个代理对象,而False
只是将truthy代理对象转换为filter
。希望False
能够处理像MyTable.fieldname
这样的无效参数。虽然我相信很多人会认为__ne__
应该始终位于比较的左侧,但事实仍然是在一般情况下没有程序化的理由强制执行这一点,并且正确的通用return not self == other
将以任何方式工作,而__ne__
只能在一个安排中工作。
ShadowRanger的def __ne__(self, other):
result = self.__eq__(other)
if result is not NotImplemented:
return not result
return NotImplemented
方法的实现是正确的(在某种意义上它的行为与默认的Python 3实现完全相同):
not self == other
亚伦霍尔执行__ne__
方法的NotImplemented
是不正确的,因为它永远不会返回not NotImplemented
(False
是__ne__
),因此具有优先权的__ne__
方法永远不会回到没有优先权的not self == other
方法。 __ne__
曾经是issue #21408方法的默认Python 3实现,但它是一个bug,它在2015年1月的Python 3.4中得到了纠正,正如ShadowRanger注意到的那样(参见chapter III Data model)。
Python 3的Python语言参考在其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)
调用NotImplemented
。如果没有为给定的参数对实现操作,那么丰富的比较方法可能会返回单例
__lt__()
。这些方法没有交换参数版本(当左参数不支持操作但右参数支持时使用);相反,
__gt__()
和__le__()
是彼此的反射,__ge__()
和__eq__()
是彼此的反射,而__ne__()
和operator_eq
是他们自己的反映。如果操作数的类型不同,并且右操作数的类型是左操作数类型的直接或间接子类,则右操作数的反射方法具有优先级,否则左操作数的方法具有优先级。不考虑虚拟子类化。
将其翻译成Python代码给出(使用==
为operator_ne
,!=
用于operator_lt
,<
用于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
方法返回not NotImplemented
时,其逆False
是bool(NotImplemented)
(如True
是NotImplemented
)而不是所需的__ne__
。
not self.__eq__(other)
正如Aaron Hall所示,__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
方法的默认实现也返回了__ne__
。
由于当__eq__
方法返回NotImplemented
时,此实现无法复制__eq__
方法的默认实现的行为,因此它是不正确的。
如果所有的__ne__
,__lt__
,__ge__
,__le__
,__gt__
和__cmp__
对这个课程都有意义,那么只需要实现qazxswpoi。否则,就像你正在做的那样,因为Daniel DiPaolo说道(当我测试它而不是查找它时;))