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


3 Answers

Вы должны быть осторожны с наследованием:

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

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

Question

При написании пользовательских классов часто бывает необходимо разрешить эквивалентность с помощью операторов == и != . В 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



Из этого ответа: https://.com/a/30676267/541136 Я продемонстрировал, что, хотя правильно определить __ne__ в терминах __eq__ - вместо

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

вы должны использовать:

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



Не был прямым ответом, но, по-видимому, был достаточно уместным, чтобы его можно было надеть, поскольку он иногда спасает многословную скуку. Вырезать прямо из документов ...

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



Тест «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

Этот код будет сравнивать только элементы данных не функциональных данных вашего класса, а также пропускать что-либо личное, что обычно является тем, что вы хотите. В случае объектов Plain Old Python у меня есть базовый класс, который реализует __init__, __str__, __repr__ и __eq__, поэтому мои объекты POPO не несут бремя всей этой дополнительной (и в большинстве случаев идентичной) логики.




Related