在编写自定义类时,通过以下方式允许等效性通常很重要 ==!= 运营商。在 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, ,身份测试(即“是 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

蟒蛇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

蟒蛇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 值为:

如果出现以下情况,数值方法和丰富的比较方法可能会返回此值 它们不对提供的操作数实现操作。( 然后,解释器将尝试反射操作,或其他一些操作 回退,具体取决于操作员。它的真值是真的。

在这种情况下,运算符将比较操作委托给 反射法其他 操作数。这 文档 将反射方法定义为:

这些方法没有交换参数版本(要使用 当 left 参数不支持操作但 right 参数时 论点确实);相当, __lt__()__gt__() 是彼此的 反射 __le__()__ge__() 是彼此的反映,并且 __eq__()__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

返回 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

其他提示

您需要小心继承:

>>> class Foo:
    def __eq__(self, other):
        if isinstance(other, self.__class__):
            return self.__dict__ == other.__dict__
        else:
            return False

>>> class Bar(Foo):pass

>>> b = Bar()
>>> f = Foo()
>>> f == b
True
>>> b == f
False

检查类型更严格地说,是这样的:

def __eq__(self, other):
    if type(other) is type(self):
        return self.__dict__ == other.__dict__
    return False

除此之外,你的方法将正常工作,这就是特殊的方法有哪些的。

您所描述的方法是我一直在做它的方式。由于它是完全通用的,你总是可以打破这种功能了成混合类以及要将这些功能继承它的类。

class CommonEqualityMixin(object):

    def __eq__(self, other):
        return (isinstance(other, self.__class__)
            and self.__dict__ == other.__dict__)

    def __ne__(self, other):
        return not self.__eq__(other)

class Foo(CommonEqualityMixin):

    def __init__(self, item):
        self.item = item

不是一个直接的答案,但似乎不够有关要上涨了,因为它可以节省一点有时冗长乏味的。从文档直切...


functools.total_ordering(CLS)

<强>给定一个类中定义的一个或多个富比较排序方法,这类装饰用品的其余部分这简化参与指定所有可能的富比较操作的努力:

在类必须定义__lt__()__le__()__gt__(),或__ge__()之一。此外,类应该提供一个__eq__()方法。

的新版本2.7

@total_ordering
class Student:
    def __eq__(self, other):
        return ((self.lastname.lower(), self.firstname.lower()) ==
                (other.lastname.lower(), other.firstname.lower()))
    def __lt__(self, other):
        return ((self.lastname.lower(), self.firstname.lower()) <
                (other.lastname.lower(), other.firstname.lower()))

您不必同时重写__eq____ne__你可以只重写__cmp__但这将会对结果的含义==,!==,<,>等。

is试验对象的身份。这意味着一个is B就在所述壳体中时True A和B都保持参考同一个对象。在Python中,你总是抱着一个变量而不是实际的对象对对象的引用,所以本质上是A是B是真实的在它们的对象应位于相同的内存位置。如何,最重要的是你为什么会去覆盖这个行为?

编辑:我不知道从__cmp__蟒3中除去,从而避免它

从这个答案: https://stackoverflow.com/a/30676267/541136 我已经证明的是,虽然它的正确而言__ne__定义__eq__ - 代替

def __ne__(self, other):
    return not self.__eq__(other)

您应该使用:

def __ne__(self, other):
    return not self == other

我认为你正在寻找的两个术语的平等(==)和身份(是)。例如:

>>> a = [1,2,3]
>>> b = [1,2,3]
>>> a == b
True       <-- a and b have values which are equal
>>> a is b
False      <-- a and b are not the same list object

在“是”的测试将使用内置“的id()”函数,其基本上返回对象的存储器地址,因此不重载用于身份检验。

然而,在测试类的平等的情况下,你可能想多一点严格对你的测试,只有在比较你的类的数据属性:

import types

class ComparesNicely(object):

    def __eq__(self, other):
        for key, value in self.__dict__.iteritems():
            if (isinstance(value, types.FunctionType) or 
                    key.startswith("__")):
                continue

            if key not in other.__dict__:
                return False

            if other.__dict__[key] != value:
                return False

         return True

这个代码将只非功能数据成员比较你的类,以及跳过任何私人通常是你想要的。在普通老式的Python对象的情况下我有一个基类,它实现__init__,__str__,__repr__和__eq__所以我的POPO对象不携带所有这些额外的(在大多数情况下是相同的)逻辑的负担。

除了使用子类/混入的,我很喜欢用一个通用类装饰

def comparable(cls):
    """ Class decorator providing generic comparison functionality """

    def __eq__(self, other):
        return isinstance(other, self.__class__) and self.__dict__ == other.__dict__

    def __ne__(self, other):
        return not self.__eq__(other)

    cls.__eq__ = __eq__
    cls.__ne__ = __ne__
    return cls

用法:

@comparable
class Number(object):
    def __init__(self, x):
        self.x = x

a = Number(1)
b = Number(1)
assert a == b
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top