编写自定义类时,通过==和!=运算符允许等效性通常很重要。在Python中,这可以通过分别实现__eq__和__ne__特殊方法来实现。我发现执行此操作的最简单方法是以下方法:
==
!=
__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__s 比较方法有什么特别的缺点吗?
__dict__s
注意:需要澄清的一点-当__eq__和__ne__未定义时,你会发现以下行为:
>>> a = Foo(1) >>> b = Foo(1) >>> a is b False >>> a == b False
也就是说,a == b评估为False因为它确实运行了a is b,所以对身份进行了测试(即“ a与b?是同一对象”)。
a == b
a is b
当__eq__和__ne__定义,你会发现这种行为(这是一个我们后):
>>> a = Foo(1) >>> b = Foo(1) >>> a is b False >>> a == b True
考虑这个简单的问题:
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__()以便操作符能够按预期运行。
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。
(x<y or 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;
class A:,class A()
class A(B)
确实继承自新类object并声明为class A(object)或class A(B):在哪里B的新类。Python 3中只被声明为新的样式类class A:,class A(object):或class A(B):。
object
class A(object)
class A(B):
B
class A:,class A(object):
对于经典风格的类,比较操作始终调用第一个操作数的方法,而对于新风格的类,则始终调用子类操作数的方法,而不管操作数的顺序如何。
所以在这里,如果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值。该文档将NotImplemented值定义为:
n1 == n3
n3 == n1
n3.__eq__
n1 != n3
n3 != n1
n3.__ne__
__ne__方
NotImplemented
如果数字方法和丰富比较方法未实现所提供操作数的操作,则可能返回此值。(然后,解释程序将根据操作员尝试执行反射操作或其他回退。)其真实值是true。
在这种情况下操作者的代表的比较操作的反射的方法的的其他操作数。该文档将反映的方法定义为:
这些方法没有交换参数版本(当left参数不支持该操作但right参数支持该操作时使用);相反,__lt__()and __gt__()是彼此的反射,__le__()and __ge__()是彼此的反射,and __eq__()and __ne__()是自己的反射。
__lt__()and __gt__()
__le__()and __ge__()
and __eq__()and __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 not NotImplemented: return not x return NotImplemented
如果操作数是不相关的类型(无继承),如果需要and 运算符的可交换性,那么即使对于新型类,也要返回NotImplemented值而不是False正确的做法。==!=
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