5

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

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?</п>

<п>Примечание: Для ясности — когда 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

5 ответ(ов)

2

Вы правы, с наследованием нужно быть осторожным. В вашем примере, когда вы используете isinstance(other, self.__class__) в методе __eq__, это может привести к неожиданным результатам. Рассмотрим ваш код:

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()
print(f == b)  # True
print(b == f)  # False

Как видно, сравнение f == b возвращает True, что может быть неожиданным, поскольку экземпляры разных классов должны быть не равны.

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

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

Таким образом, вы будете уверены, что два объекта равны только если они экземпляры одного и того же класса. Однако ваш изначальный подход также работает, поскольку специальные методы, такие как __eq__, предназначены именно для этой цели. Но нужна осторожность, чтобы избежать неожиданных результатов при работе с наследованием.

1

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

Такое решение позволит вам легко переиспользовать логику сравнения объектов в любых классах, просто унаследовав от вашего CommonEqualityMixin.

0

Вы не обязаны переопределять как __eq__, так и __ne__, вы можете переопределить только __cmp__, но это повлияет на результат операторов ==, !=, <, > и так далее.

Оператор is проверяет идентичность объектов. Это означает, что выражение a is b будет истинно только в случае, если a и b ссылаются на один и тот же объект. В Python вы всегда храните ссылку на объект в переменной, а не сам объект, поэтому для того, чтобы a is b оказался истинным, объекты должны находиться по одному и тому же адресу в памяти. Но зачем и, что более важно, почему вы хотели бы переопределить такое поведение?

Дополнение: я не знал, что __cmp__ был удалён в Python 3, поэтому избегайте его использования.

0

Для того чтобы продемонстрировать разницу, обратите внимание на следующий вопрос: следует ли определять метод __ne__, исходя из метода __eq__, или же лучше использовать сравнение с помощью оператора ==? В ответе на ваш вопрос (https://stackoverflow.com/a/30676267/541136) я показал, что правильнее использовать следующее определение:

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

Такой подход предпочтительнее, чем определение __ne__ через __eq__:

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

Это связано с тем, что использование not self == other лучше сохраняет инкапсуляцию и позволяет избежать потенциальных проблем с рекурсией, если __eq__ будет переопределен ненадлежащим образом.

0

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

>>> a = [1, 2, 3]
>>> b = [1, 2, 3]
>>> a == b
True       <-- a и b имеют равные значения
>>> a is b
False      <-- a и b не являются одним и тем же объектом списка
Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь