在Python类中支持等效性(“平等性")的优雅方法 [英] Elegant ways to support equivalence ("equality") in Python classes
问题描述
在编写自定义类时,通过 ==
和!=
运算符允许等效性通常很重要.在Python中,这可以通过分别实现 __ eq __
和 __ ne __
特殊方法来实现.我发现执行此操作的最简单方法是以下方法:
When writing custom classes it is often important to allow equivalence by means of the ==
and !=
operators. In Python, this is made possible by implementing the __eq__
and __ne__
special methods, respectively. The easiest way I've found to do this is the following method:
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的方法有什么特别的缺点吗?
Do you know of more elegant means of doing this? Do you know of any particular disadvantages to using the above method of comparing __dict__
s?
注意:需要澄清一点-当未定义 __ eq __
和 __ ne __
时,您会发现以下行为:
Note: A bit of clarification--when __eq__
and __ne__
are undefined, you'll find this behavior:
>>> a = Foo(1)
>>> b = Foo(1)
>>> a is b
False
>>> a == b
False
也就是说, a == b
评估为 False
,因为它确实运行 a is b
,这是对身份的测试(即," a
与 b
是同一对象吗?").
That is, a == b
evaluates to False
because it really runs a is b
, a test of identity (i.e., "Is a
the same object as b
?").
定义 __ eq __
和 __ ne __
后,您会发现这种行为(这是我们要遵循的行为):
When __eq__
and __ne__
are defined, you'll find this behavior (which is the one we're after):
>>> 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使用对象标识符进行比较操作:
So, Python by default uses the object identifiers for comparison operations:
id(n1) # 140400634555856
id(n2) # 140400634555920
重写 __ eq __
函数似乎可以解决问题:
Overriding the __eq__
function seems to solve the problem:
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 __
函数,如
In Python 2, always remember to override the __ne__
function as well, as the documentation states:
比较运算符之间没有隐含的关系.这
x == y
的真相并不表示x!= y
为假.因此,当定义__ eq __()
,还应该定义__ ne __()
操作员将表现出预期的效果.
There are no implied relationships among the comparison operators. The truth of
x==y
does not imply thatx!=y
is false. Accordingly, when defining__eq__()
, one should also define__ne__()
so that the operators will behave as expected.
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 中,这不再是必需的,因为文档状态:
In Python 3, this is no longer necessary, as the documentation states:
默认情况下,
__ ne __()
委托给__ eq __()
并反转结果除非是NotImplemented
.没有其他暗示比较运算符之间的关系,例如真相(x< y或x == y)
的含义并不意味着x< = y
.
By default,
__ne__()
delegates to__eq__()
and inverts the result unless it isNotImplemented
. There are no other implied relationships among the comparison operators, for example, the truth of(x<y or x==y)
does not implyx<=y
.
但是,这并不能解决我们所有的问题.让我们添加一个子类:
But that does not solve all our problems. Let’s add a subclass:
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具有两种类:
Note: Python 2 has two kinds of classes:
-
经典-样式 (或旧样式)类,它们不是从
object
继承,并声明为A类:
,A类():
或A类(B):
其中,B
是经典样式课;
classic-style (or old-style) classes, that do not inherit from
object
and that are declared asclass A:
,class A():
orclass A(B):
whereB
is a classic-style class;
新-样式 类,它们确实从 object
继承,并声明为 class A(object)
或 class A(B):
,其中 B
是一种新型类.Python 3仅具有声明为 class A:
, class A(object):
或 class A(B):
的新型类..
new-style classes, that do inherit from object
and that are declared as class A(object)
or class A(B):
where B
is a new-style class. Python 3 has only new-style classes that are declared as class A:
, class A(object):
or class A(B):
.
对于经典样式的类,比较操作始终调用第一个操作数的方法,而对于新型样式的类,则始终调用子类操作数的方法
For classic-style classes, a comparison operation always calls the method of the first operand, while for new-style classes, it always calls the method of the subclass operand, regardless of the order of the operands.
所以在这里,如果 Number
是经典样式的类:
So here, if Number
is a classic-style class:
-
n1 == n3
调用n1 .__ eq __
; -
n3 == n1
调用n3 .__ eq __
; -
n1!= n3
调用n1 .__ ne __
; -
n3!= n1
调用n3 .__ ne __
.
n1 == n3
callsn1.__eq__
;n3 == n1
callsn3.__eq__
;n1 != n3
callsn1.__ne__
;n3 != n1
callsn3.__ne__
.
如果 Number
是新型类:
-
n1 == n3
和n3 == n1
都调用n3 .__ eq __
; -
n1!= n3
和n3!= n1
都调用n3 .__ ne __
.
- both
n1 == n3
andn3 == n1
calln3.__eq__
; - both
n1 != n3
andn3 != n1
calln3.__ne__
.
要修复Python 2经典样式类 __ eq __
和 ==
和!=
运算符的不交换性问题当不支持操作数类型时, __ ne __
方法应返回 NotImplemented
值.文档定义了 NotImplemented
值为:
To fix the non-commutativity issue of the ==
and !=
operators for Python 2 classic-style classes, the __eq__
and __ne__
methods should return the NotImplemented
value when an operand type is not supported. The documentation defines the NotImplemented
value as:
如果满足以下条件,则数字方法和丰富的比较方法可能会返回此值:它们不为提供的操作数实现操作.(这解释器将尝试执行反射操作或其他操作回退,具体取决于操作员.)其真实值是true.
Numeric methods and rich comparison methods may return this value if they do not implement the operation for the operands provided. (The interpreter will then try the reflected operation, or some other fallback, depending on the operator.) Its truth value is true.
在这种情况下,运算符将比较操作委托给 other 操作数的 reflected方法.文档将反映的方法定义为:
In this case the operator delegates the comparison operation to the reflected method of the other operand. The documentation defines reflected methods as:
这些方法没有交换参数版本(要使用当左参数不支持该操作但右参数争论确实);相反,
__ lt __()
和__ gt __()
是彼此的反射,__ le __()
和__ ge __()
是彼此的反射,并且__ eq __()
和__ ne __()
是他们自己的反映.
There are no swapped-argument versions of these methods (to be used when the left argument does not support the operation but the right argument does); rather,
__lt__()
and__gt__()
are each other’s reflection,__le__()
and__ge__()
are each other’s reflection, and__eq__()
and__ne__()
are their own reflection.
结果如下:
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
也是正确的选择当操作数是无关类型(无继承)时,需要== !=
运算符.
Returning the NotImplemented
value instead of False
is the right thing to do even for new-style classes if commutativity of the ==
and !=
operators is desired when the operands are of unrelated types (no inheritance).
我们到了吗?不完全的.我们有多少个唯一数字?
Are we there yet? Not quite. How many unique numbers do we have?
len(set([n1, n2, n3])) # 3 -- oops
集合使用对象的哈希值,默认情况下,Python返回对象标识符的哈希值.让我们尝试覆盖它:
Sets use the hashes of objects, and by default Python returns the hash of the identifier of the object. Let’s try to override it:
def __hash__(self):
"""Overrides the default implementation"""
return hash(tuple(sorted(self.__dict__.items())))
len(set([n1, n2, n3])) # 1
最终结果如下(我在末尾添加了一些断言以进行验证):
The end result looks like this (I added some assertions at the end for validation):
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
这篇关于在Python类中支持等效性(“平等性")的优雅方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!