考虑这个简单的问题:
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 or 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 有两种类:
对于经典风格的类,比较操作总是调用第一个操作数的方法,而对于新风格的类,它总是调用子类操作数的方法,而不管操作数的顺序如何。
所以在这里,ifNumber
是一个经典风格的类:
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 经典样式类的==
and运算符的非交换性问题,and方法应该在不支持操作数类型时返回值。该文档将值定义为:!=
__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
如果操作数是不相关类型(无继承)时需要and运算符的可交换性,即使对于新型类,返回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