在Python类中支持等效性(“平等性")的优雅方法 [英] Elegant ways to support equivalence ("equality") in Python classes

查看:72
本文介绍了在Python类中支持等效性(“平等性")的优雅方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在编写自定义类时,通过 == != 运算符允许等效性通常很重要.在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 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 中,这不再是必需的,因为文档状态:

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 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.

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

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 as class A:, class A(): or class A(B): where B 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 calls n1.__eq__;
  • n3 == n1 calls n3.__eq__;
  • n1 != n3 calls n1.__ne__;
  • n3 != n1 calls n3.__ne__.

如果 Number 是新型类:

  • n1 == n3 n3 == n1 都调用 n3 .__ eq __ ;
  • n1!= n3 n3!= n1 都调用 n3 .__ ne __ .
  • both n1 == n3 and n3 == n1 call n3.__eq__;
  • both n1 != n3 and n3 != n1 call n3.__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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆