在Python类中支持等价(“平等”)的优雅方法

在Python类中支持等价(“平等”)的优雅方法,第1张

在Python类中支持等价(“平等”)的优雅方法

考虑这个简单的问题:

class Number:    def __init__(self, number):        self.number = numbern1 = Number(1)n2 = Number(1)n1 == n2 # False -- oops

因此,默认情况下,Python使用对象标识符进行比较 *** 作

id(n1) # 140400634555856id(n2) # 140400634555920

覆盖

__eq__
函数似乎可以解决问题:

def __eq__(self, other):    """Overrides the default implementation"""    if isinstance(other, Number):        return self.number == other.number    return Falsen1 == n2 # Truen1 != 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 # Truen1 != n2 # False

在Python 3中,不再需要这样做,因为文档指出:

默认情况下,除非为,否则将

__ne__()
委托给
__eq__()
结果并将其反转NotImplemented。比较运算符之间没有其他隐含关系,例如,的真相
(x<y or x==y)
并不意味着x<=y。

但这不能解决我们所有的问题。让我们添加一个子类:

class SubNumber(Number):    passn3 = SubNumber(1)n1 == n3 # False for classic-style classes -- oops, True for new-style classesn3 == n1 # Truen1 != n3 # True for classic-style classes -- oops, False for new-style classesn3 != 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值定义为:

如果数字方法和丰富比较方法未实现所提供 *** 作数的 *** 作,则可能返回此值。(然后,解释程序将根据 *** 作员尝试执行反射 *** 作或其他回退。)其真实值是true。

在这种情况下 *** 作者的代表的比较 *** 作的反射的方法的的其他 *** 作数。该文档将反映的方法定义为:

这些方法没有交换参数版本(当left参数不支持该 *** 作但right参数支持该 *** 作时使用);相反,

__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 NotImplementeddef __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
正确的做法。
==!=

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

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):    passn1 = Number(1)n2 = Number(1)n3 = SubNumber(1)n4 = SubNumber(4)assert n1 == n2assert n2 == n1assert not n1 != n2assert not n2 != n1assert n1 == n3assert n3 == n1assert not n1 != n3assert not n3 != n1assert not n1 == n4assert not n4 == n1assert n1 != n4assert n4 != n1assert len(set([n1, n2, n3, ])) == 1assert len(set([n1, n2, n3, n4])) == 2


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5639226.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-16
下一篇 2022-12-16

发表评论

登录后才能评论

评论列表(0条)

保存