在编写自定义类时,通过==
和!=
运算符允许等效通常很重要.在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__
?
注意:有点澄清 - 何时__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__
定义,你会发现这种行为(这是一个我们后):
>>> a = Foo(1) >>> b = Foo(1) >>> a is b False >>> a == b True
Tal Weiss.. 289
考虑这个简单的问题:
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
并非暗示 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
的一个经典风格的类;
新样式的类,它们继承object
并声明为class A(object)
或者class A(B):
在哪里B
是新式类.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
电话n3.__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 not NotImplemented: return not x return NotImplemented
如果操作数是不相关的类型(没有继承),那么如果需要和运算符的交换,那么返回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
很棒的总结,但你[应该使用`==`而不是`__eq__`实现`__ne__`](/sf/ask/17360801/). (11认同)
`hash(tuple(sorted(self .__ dict __.items())))`如果在`self .__ dict__`的值中有任何不可清除的对象,那么`将不起作用(即,如果有任何属性)对象设置为,例如,`list`). (3认同)
是的,但是如果你的vars()中有这样的可变对象,那么这两个对象并不是真的相等...... (3认同)
他问起优雅,但他变得健壮。 (2认同)
Algorias.. 195
你需要小心继承:
>>> class Foo: def __eq__(self, other): if isinstance(other, self.__class__): return self.__dict__ == other.__dict__ else: return False >>> class Bar(Foo):pass >>> b = Bar() >>> f = Foo() >>> f == b True >>> b == f False
更严格地检查类型,如下所示:
def __eq__(self, other): if type(other) is type(self): return self.__dict__ == other.__dict__ return False
除此之外,您的方法将正常工作,这就是特殊方法.
考虑这个简单的问题:
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
并非暗示 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
的一个经典风格的类;
新样式的类,它们继承object
并声明为class A(object)
或者class A(B):
在哪里B
是新式类.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
电话n3.__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 not NotImplemented: return not x return NotImplemented
如果操作数是不相关的类型(没有继承),那么如果需要和运算符的交换,那么返回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
你需要小心继承:
>>> class Foo: def __eq__(self, other): if isinstance(other, self.__class__): return self.__dict__ == other.__dict__ else: return False >>> class Bar(Foo):pass >>> b = Bar() >>> f = Foo() >>> f == b True >>> b == f False
更严格地检查类型,如下所示:
def __eq__(self, other): if type(other) is type(self): return self.__dict__ == other.__dict__ return False
除此之外,您的方法将正常工作,这就是特殊方法.
你描述的方式是我一直以来的方式.由于它完全是通用的,因此您可以始终将该功能分解为mixin类,并在需要该功能的类中继承它.
class CommonEqualityMixin(object): def __eq__(self, other): return (isinstance(other, self.__class__) and self.__dict__ == other.__dict__) def __ne__(self, other): return not self.__eq__(other) class Foo(CommonEqualityMixin): def __init__(self, item): self.item = item
这不是一个直接的答案,但似乎有足够的相关性,因为它有时会节省一些冗长的单调乏味.直接从文档中删除...
functools.total_ordering(CLS)
给定一个定义一个或多个丰富的比较排序方法的类,这个类装饰器提供其余的.这简化了指定所有可能的丰富比较操作所涉及的工作:
该类必须定义lt(),le(),gt()或ge()中的一个.此外,该类应提供eq()方法.
版本2.7中的新功能
@total_ordering class Student: def __eq__(self, other): return ((self.lastname.lower(), self.firstname.lower()) == (other.lastname.lower(), other.firstname.lower())) def __lt__(self, other): return ((self.lastname.lower(), self.firstname.lower()) < (other.lastname.lower(), other.firstname.lower()))
您不必覆盖它们__eq__
,__ne__
只能覆盖,__cmp__
但这会对==,!==,<,>等结果产生影响.
is
测试对象身份.这意味着当a和b都保持对同一对象的引用时,is
b将True
处于这种情况.在python中,你始终对变量中的对象而不是实际对象进行引用,因此对于a来说,a b是真的,它们中的对象应该位于同一个内存位置.你最重要的是为什么要重写这种行为?
编辑:我不知道__cmp__
是从python 3中删除所以避免它.
从这个答案:https : //stackoverflow.com/a/30676267/541136我已经证明了这一点,尽管__ne__
用术语定义是正确的__eq__
-而不是
def __ne__(self, other): return not self.__eq__(other)
您应该使用:
def __ne__(self, other): return not self == other