编写自定义类时,通过 ==
和 !=
运算符允许等效通常很重要 . 在Python中,这可以通过分别实现 __eq__
和 __ne__
特殊方法来实现 . 我发现这样做的最简单方法是以下方法:
class Foo:
def __init__(self, item):
self.item = item
def __eq__(self, other):
if isinstance(other, self.__class__):
return self.__dict__ == other.__dict__
else:
return False
def __ne__(self, other):
return not self.__eq__(other)
你知道更优雅的做法吗?您是否知道使用上述比较 __dict__
的方法有什么特别的缺点?
Note :有点澄清 - 当 __eq__
和 __ne__
未定义时,你会发现这种行为:
>>> a = Foo(1)
>>> b = Foo(1)
>>> a is b
False
>>> a == b
False
也就是说, a == b
计算为 False
,因为它确实运行 a is b
,一个身份测试(即“ a
与 b
相同的对象?”) .
当 __eq__
和 __ne__
被定义时,你'll find this behavior (which is the one we'之后):
>>> a = Foo(1)
>>> b = Foo(1)
>>> a is b
False
>>> a == b
True
8 回答
考虑这个简单的问题:
因此,Python默认使用对象标识符进行比较操作:
覆盖
__eq__
函数似乎解决了这个问题:在Python 2中,始终记得覆盖
__ne__
函数,因为documentation表示:在Python 3中,这不再是必需的,因为documentation指出:
但这并不能解决我们所有的问题 . 让我们添加一个子类:
Note: Python 2有两种类:
classic-style(或旧式)类,不从
object
继承,并且声明为class A:
,class A():
或class A(B):
,其中B
是经典风格的类;new-style类,继承自
object
并声明为class A(object)
或class A(B):
,其中B
是新式类 . Python 3只有新式的类,声明为class A:
,class A(object):
或class A(B):
.对于经典样式类,比较操作总是调用第一个操作数的方法,而对于新样式类,它总是调用子类操作数regardless of the order of the operands的方法 .
所以在这里,如果
Number
是一个经典风格的类:n1 == n3
来电n1.__eq__
;n3 == n1
来电n3.__eq__
;n1 != n3
来电n1.__ne__
;n3 != n1
来电n3.__ne__
.如果
Number
是一个新式的类:n1 == n3
和n3 == n1
都叫n3.__eq__
;n1 != n3
和n3 != n1
都致电n3.__ne__
.要修复Python 2经典样式类的
==
和!=
运算符的非交换性问题,__eq__
和__ne__
方法应在不支持操作数类型时返回NotImplemented
值 . documentation将NotImplemented
值定义为:在这种情况下,运算符将比较操作委托给另一个操作数的反射方法 . documentation将反射方法定义为:
结果如下:
如果操作数是不相关的类型(没有继承),那么当需要
==
和!=
运算符的交换时,返回NotImplemented
值而不是False
是正确的做法 .我们到了吗?不完全的 . 我们有多少个唯一号码?
集使用对象的哈希值,默认情况下,Python返回对象标识符的哈希值 . 让我们试着覆盖它:
最终结果看起来像这样(我在最后添加了一些断言验证):
你需要小心继承:
更严格地检查类型,如下所示:
除此之外,您的方法将正常工作,这就是特殊方法 .
你描述的方式是我一直以来的方式 . 由于它完全是通用的,因此您可以始终将该功能分解为mixin类,并在需要该功能的类中继承它 .
这不是一个直接的答案,但似乎有足够的相关性,因为它有时会节省一些冗长的单调乏味 . 直接从文档中删除...
functools.total_ordering(cls)
Given a class defining one or more rich comparison ordering methods, this class decorator supplies the rest. 这简化了指定所有可能的丰富比较操作所涉及的工作:
该类必须定义 lt (), le (), gt ()或 ge ()之一 . 此外,该类应提供 eq ()方法 .
版本2.7中的新功能
您不必覆盖
__eq__
和__ne__
,您只能覆盖__cmp__
,但这会对==,!==,<,>等结果产生影响 .is
测试对象标识 . 这意味着在a和b都保持对同一对象的引用的情况下is
b将是True
. 在python中,你始终对变量中的对象而不是实际对象进行引用,因此对于a来说,a b是真的,它们中的对象应该位于同一个内存位置 . 你最重要的是为什么要重写这种行为?编辑:我不知道
__cmp__
是从python 3中删除所以避免它 .从这个回答:https://stackoverflow.com/a/30676267/541136我已经证明,虽然用
__eq__
定义__ne__
是正确的 - 而不是你应该使用:
我认为您正在寻找的两个术语是 equality (==)和 identity (是) . 例如:
'is'测试将使用内置的'id()'函数测试身份,该函数基本上返回对象的内存地址,因此不可重载 .
但是,在测试类的相等性的情况下,您可能希望对测试稍微严格一些,并且只比较类中的数据属性:
此代码仅比较您的类的非函数数据成员以及跳过任何私有的,这通常是您想要的 . 在Plain Old Python Objects的情况下,我有一个基类,它实现了__init , ttr __,repr__和__eq,因此我的POPO对象不承担所有额外(在大多数情况下是相同的)逻辑的负担 .