考虑这个简单的问题:
class Number:
def __init__(self, number):
self.number = number
n1 = Number(1)
n2 = Number(1)
n1 == n2 # False -- oops
因此,Python默认使用对象标识符进行比较操作:
id(n1) # 140400634555856
id(n2) # 140400634555920
重写__eq__函数似乎可以解决这个问题:
def __eq__(self, other):
"""Overrides the default implementation"""
if isinstance(other, Number):
return self.number == other.number
return False
n1 == n2 # True
n1 != n2 # True in Python 2 -- oops, False in Python 3
在Python 2中,始终记得重写__ne__函数,正如文档所述:
比较操作符之间没有隐含的关系。的
x==y的真理并不意味着x!=y为假。因此,当
定义__eq__()时,还应该定义__ne__(),以便
操作符将按照预期行事。
def __ne__(self, other):
"""Overrides the default implementation (unnecessary in Python 3)"""
return not self.__eq__(other)
n1 == n2 # True
n1 != n2 # False
在Python 3中,这不再是必要的,正如文档所述:
默认情况下,__ne__()委托给__eq__()并反转结果
除非它是NotImplemented。没有其他暗示
比较运算符之间的关系,例如,真值
的(x<y或x==y)并不意味着x<=y。
但这并不能解决我们所有的问题。让我们添加一个子类:
class SubNumber(Number):
pass
n3 = SubNumber(1)
n1 == n3 # False for classic-style classes -- oops, True for new-style classes
n3 == n1 # True
n1 != n3 # True for classic-style classes -- oops, False for new-style classes
n3 != n1 # False
注意:Python 2有两类:
经典风格(或老式)类,不从object继承,并声明为class A:, class A():或class A(B):,其中B是经典风格类;
new-style类,继承自object并声明为类A(object)或类A(B):其中B是一个new-style类。Python 3只有新风格的类,声明为class A:、class A(object):或class A(B):。
对于经典风格的类,比较操作总是调用第一个操作数的方法,而对于新风格的类,它总是调用子类操作数的方法,而不管操作数的顺序如何。
所以这里,如果Number是一个经典风格的类:
N1 == n3调用N1 .__eq__;
N3 == n1调用N3 .__eq__;
N1 != n3调用N1 .__ne__;
N3 != n1调用3.__ne__。
如果Number是一个新型类:
n1 == n3和n3 == n1调用n3.__eq__;
n1 != n3和n3 != n1调用n3.__ne__。
为了修复Python 2经典风格类的==和!=操作符的不可交换性问题,当不支持操作数类型时,__eq__和__ne__方法应该返回NotImplemented值。文档将NotImplemented值定义为:
数值方法和丰富的比较方法可以返回此值,如果
它们不为所提供的操作数实现操作。(
解释器将尝试反射的操作或其他操作
后退,取决于操作人员。)它的真值为真。
在这种情况下,操作符将比较操作委托给另一个操作数的反射方法。文档将反射方法定义为:
这些方法没有参数互换的版本
当左参数不支持操作而支持右参数时
参数);相反,__lt__()和__gt__()是彼此的
反射,__le__()和__ge__()是彼此的反射,并且
__eq__()和__ne__()是它们自己的反射。
结果如下所示:
def __eq__(self, other):
"""Overrides the default implementation"""
if isinstance(other, Number):
return self.number == other.number
return NotImplemented
def __ne__(self, other):
"""Overrides the default implementation (unnecessary in Python 3)"""
x = self.__eq__(other)
if x is NotImplemented:
return NotImplemented
return not x
返回NotImplemented值而不是False是正确的做法,即使对于新类型的类,如果操作数类型不相关(没有继承),需要==和!=操作符的可交换性。
我们到了吗?不完全是。我们有多少个唯一的数字?
len(set([n1, n2, n3])) # 3 -- oops
集合使用对象的哈希值,默认情况下Python返回对象标识符的哈希值。让我们试着重写它:
def __hash__(self):
"""Overrides the default implementation"""
return hash(tuple(sorted(self.__dict__.items())))
len(set([n1, n2, n3])) # 1
最终结果是这样的(我在最后添加了一些断言进行验证):
class Number:
def __init__(self, number):
self.number = number
def __eq__(self, other):
"""Overrides the default implementation"""
if isinstance(other, Number):
return self.number == other.number
return NotImplemented
def __ne__(self, other):
"""Overrides the default implementation (unnecessary in Python 3)"""
x = self.__eq__(other)
if x is not NotImplemented:
return not x
return NotImplemented
def __hash__(self):
"""Overrides the default implementation"""
return hash(tuple(sorted(self.__dict__.items())))
class SubNumber(Number):
pass
n1 = Number(1)
n2 = Number(1)
n3 = SubNumber(1)
n4 = SubNumber(4)
assert n1 == n2
assert n2 == n1
assert not n1 != n2
assert not n2 != n1
assert n1 == n3
assert n3 == n1
assert not n1 != n3
assert not n3 != n1
assert not n1 == n4
assert not n4 == n1
assert n1 != n4
assert n4 != n1
assert len(set([n1, n2, n3, ])) == 1
assert len(set([n1, n2, n3, n4])) == 2