Элегантные способы поддержки эквивалентности ("равенства") в классах 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?</п>
<п>Примечание: Для ясности — когда 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 ответ(ов)
Вы правы, с наследованием нужно быть осторожным. В вашем примере, когда вы используете 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__, предназначены именно для этой цели. Но нужна осторожность, чтобы избежать неожиданных результатов при работе с наследованием.
Вы описали способ, который я всегда использовал. Поскольку он абсолютно универсален, вы можете выделить эту функциональность в отдельный класс-примеситель (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.
Вы не обязаны переопределять как __eq__, так и __ne__, вы можете переопределить только __cmp__, но это повлияет на результат операторов ==, !=, <, > и так далее.
Оператор is проверяет идентичность объектов. Это означает, что выражение a is b будет истинно только в случае, если a и b ссылаются на один и тот же объект. В Python вы всегда храните ссылку на объект в переменной, а не сам объект, поэтому для того, чтобы a is b оказался истинным, объекты должны находиться по одному и тому же адресу в памяти. Но зачем и, что более важно, почему вы хотели бы переопределить такое поведение?
Дополнение: я не знал, что __cmp__ был удалён в Python 3, поэтому избегайте его использования.
Для того чтобы продемонстрировать разницу, обратите внимание на следующий вопрос: следует ли определять метод __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__ будет переопределен ненадлежащим образом.
Думаю, что два термина, которые вам нужны, это равенство (==) и идентичность (is). Например:
>>> a = [1, 2, 3]
>>> b = [1, 2, 3]
>>> a == b
True <-- a и b имеют равные значения
>>> a is b
False <-- a и b не являются одним и тем же объектом списка
Почему сравнение строк с помощью '==' и 'is' иногда дает разные результаты?
Сравнение строк в Python: is vs. ==
Как изменить порядок столбцов в DataFrame?
'pip' не распознан как командa внутреннего или внешнего формата
Почему statistics.mean() работает так медленно?