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