gpt4 book ai didi

python - 在 Python 类中支持等价性 ("equality") 的优雅方法

转载 作者:太空宇宙 更新时间:2023-11-03 21:41:33 24 4
gpt4 key购买 nike

编写自定义类时,通过 ==!= 运算符允许等效性通常很重要。在 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,这是一个身份测试(即“ab 是同一个对象吗?”)。

当定义 __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__功能也如此,如 documentation状态:

There are no implied relationships among the comparison operators. The truth of x==y does not imply that x!=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中,这不再是必要的,因为 documentation状态:

By default, __ne__() delegates to __eq__() and inverts the result unless it is NotImplemented. There are no other implied relationships among the comparison operators, for example, the truth of (x<y or x==y) does not imply 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 有两种类:

  • classic-style (或旧式)类,继承自object并声明为 class A: , class A():class A(B):哪里B是一个经典风格的类;

  • new-style 类,继承自 object并声明为 class A(object)class A(B):哪里B是一个新式的类(class)。 Python 3 仅具有声明为 class A: 的新式类。 , class A(object):class A(B):

对于经典风格的类,比较操作总是调用第一个操作数的方法,而对于新风格的类,它总是调用子类操作数的方法,regardless of the order of the operands

所以在这里,如果 Number是一个经典风格的类:

  • n1 == n3来电 n1.__eq__ ;
  • n3 == n1来电 n3.__eq__ ;
  • n1 != n3来电 n1.__ne__ ;
  • n3 != n1来电 n3.__ne__ .

如果 Number是一个新式的类:

  • 两者n1 == n3n3 == n1调用n3.__eq__ ;
  • 两者n1 != n3n3 != n1调用n3.__ne__ .

修复 == 的非交换性问题和!= Python 2 经典风格类的运算符 __eq____ne__方法应返回 NotImplemented不支持操作数类型时的值。 documentation定义 NotImplemented值为:

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.

在这种情况下,运算符将比较操作委托(delegate)给其他操作数的反射方法documentation将反射方法定义为:

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如果 ==可交换性,即使对于新式类也是正确的做法和!=当操作数属于不相关类型(无继承)时,需要使用运算符。

我们到了吗?不完全的。我们有多少个唯一的号码?

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

关于python - 在 Python 类中支持等价性 ("equality") 的优雅方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52817353/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com