Элегантные способы поддержки эквивалентности ("равенства") в классах Python


при написании пользовательских классов часто важно разрешить эквивалентность с помощью == и != операторы. В 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, тест идентичности (т. е. " Is a тот же объект, что и b?").

, когда __eq__ и __ne__ определены, вы найдете это поведение (которое мы ищем):

>>> a = Foo(1)
>>> b = Foo(1)
>>> a is b
False
>>> a == b
True
8 314

8 ответов:

рассмотрим такую простую задачу:

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

In Python 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

кроме того, ваш подход будет работать нормально, что существуют специальные методы.

то, как вы описываете, это то, как я всегда это делал. Поскольку он полностью универсален, вы всегда можете разбить эту функциональность на класс mixin и наследовать ее в классах, где вы хотите эту функциональность.

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)

учитывая класс, определяющий один или несколько богатых методов сравнения заказа, этот декоратор класса поставляет остальное. это упрощает работу, связанную с указанием всех возможных операций сравнения rich:

класс должен определите один из 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 б True в случае, когда a и b оба содержат ссылку на один и тот же объект. В python вы всегда держите ссылку на объект в переменной, а не на фактический объект, поэтому, по сути, для того, чтобы a был истинным, объекты в них должны быть расположены в той же ячейке памяти. Как и главное зачем вы собираетесь переопределить это поведение?

Edit: я не знал __cmp__ был удален из python 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

Я думаю, что два термина, которые вы ищете равенство (==) и личность (is). Например:

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

тест " is "будет проверять идентичность, используя встроенную функцию" 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 не несут бремя всей этой дополнительной (и в большинстве случаев идентичной) логики.