当前位置: 首页 > 面试题库 >

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

曾昂然
2023-03-14
问题内容

编写自定义类时,通过==!=运算符允许等效性通常很重要。在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__s 比较方法有什么特别的缺点吗?

注意:需要澄清的一点-当__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

问题答案:

考虑这个简单的问题:

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<y or x==y)并不意味着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 == n3n3 == n1打电话n3.__eq__;
n1 != n3n3 != 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 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正确的做法。==!=

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

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中,您可以执行以下操作: 或这个: 在Go中,最简单的选项是: 这不允许您交换格式字符串中参数的顺序,而这需要对I18N进行。Go 确实 具有该软件包,这将需要以下内容: 这似乎是一条很长的路要走的错误消息。有没有更合理的选择,可以让我给出与顺序无关的字符串参数? 问题答案: 用 使用,实现您所需的格式化程序非常容易且紧凑。 输出(在Go Playground上尝试):

  • 我尝试使用以下R语句,并使用NumPy将其转换为Python: 有与which()等价的Python吗?这里,x是矩阵tmp中的行,k对应于另一个矩阵中的列数。 之前,我尝试了以下Python代码,并收到一个值错误(操作数无法与形状一起广播):

  • 等价 cljs 的数据结构是在 js 基础之上实现的. 数值类型的数据可以直接判断. 一般通过 (= a b) 判断 a 和 b 的内容是否一致. Collection 类型数据除了 = 函数之外, 还可以使用 identical? 函数判断两个数据的引用是否一致. (identical? {} {}) ; true (identical? {:a 1} {:a 1}) ; false (= {:

  • 问题内容: 在Python 2.x中,当您要将一个方法标记为抽象时,可以这样定义它: 然后,如果您忘记覆盖它,则会收到一个很好的提醒异常。是否存在将字段标记为抽象的等效方法?还是在您可以做的所有工作中在类文档字符串中说明? 起初我以为可以将字段设置为NotImplemented,但是当我查看它的实际用途(进行大量比较)时,它似乎很脏。 问题答案: 是的你可以。使用装饰器。例如,如果您有一个名为“e

  • 问题内容: 我需要从一些文本文件中选择一些数字。我可以使用grep选择所需的行,但是不知道如何从行中提取数字。一位同事向我展示了如何使用Perl从bash中做到这一点: 但是,我通常使用Python而不是Perl进行编码。所以我的问题是,我可以用相同的方式使用Python吗?即,我可以将一些东西从bash传递到Python,然后直接将结果传递到stdout吗?…如果有道理。还是在这种情况下Perl

  • 我想将名称(ClassName)更改为一个不同的名称,例如(ClassA)。我可以通过编写SPARQL查询来实现吗? 作为一个工具包,我正在使用gena,我不确定我所询问的是可能的还是不可能的!