При написании пользовательских классов часто важно , чтобы эквивалентность с помощью ==
и !=
операторов. В 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
python
equality
equivalence
получили
источник
источник
is
оператор, позволяющий отличать идентичность объекта от сравнения значений.Ответы:
Рассмотрим эту простую проблему:
Итак, Python по умолчанию использует идентификаторы объектов для операций сравнения:
Переопределение
__eq__
функции, кажется, решает проблему:В Python 2 всегда не забывайте переопределять
__ne__
функцию, так как в документации говорится:В Python 3 это больше не требуется, поскольку в документации говорится:
Но это не решает всех наших проблем. Давайте добавим подкласс:
Примечание: Python 2 имеет два вида классов:
в классическом стиле (или в старом стиле ) классов, которые не наследуют
object
и которые объявлены какclass A:
,class A():
илиclass A(B):
гдеB
классклассическом стиле;классы нового стиля , которые наследуются
object
и которые объявлены какclass A(object)
илиclass A(B):
гдеB
находится класс нового стиля. Python 3 имеет только классы нового стиля, которые заявленыкачествеclass A:
,class A(object):
илиclass A(B):
.Для классов классического стиля операция сравнения всегда вызывает метод первого операнда, в то время как для классов нового стиля она всегда вызывает метод операнда подкласса, независимо от порядка операндов .
Итак, если
Number
класс классического стиля:n1 == n3
звонкиn1.__eq__
;n3 == n1
звонкиn3.__eq__
;n1 != n3
звонкиn1.__ne__
;n3 != n1
звонкиn3.__ne__
.И если
Number
класс нового стиля:n1 == n3
иn3 == n1
вызовn3.__eq__
;n1 != n3
иn3 != n1
назватьn3.__ne__
.Чтобы устранить проблему , не коммутативность
==
и!=
оператор для Python 2 класса в классическом стиле, то__eq__
и__ne__
методы должны возвращатьNotImplemented
значение , когда тип операнда не поддерживаются. Документация определяетNotImplemented
значение как:В этом случае делегаты оператора операция сравнения на отражение метод от другого операнда. В документации определяет отражение методы , как:
Результат выглядит так:
Возвращение
NotImplemented
значения вместоFalse
является правильным , что нужно сделать , даже для новых классов , если коммутативности из==
и!=
операторов желательно , когда операнды неродственных типов (без наследования).Мы уже на месте? Не совсем. Сколько у нас уникальных номеров?
Наборы используют хеши объектов, и по умолчанию Python возвращает хеш идентификатора объекта. Давайте попробуем переопределить это:
Конечный результат выглядит следующим образом (в конце я добавил несколько утверждений для проверки):
источник
hash(tuple(sorted(self.__dict__.items())))
не будет работать, если среди значений объекта есть какие-либо не хэшируемые объектыself.__dict__
(т. е. если для любого из атрибутов объекта установлено, скажем, alist
).__ne__
использование==
вместо__eq__
.__ne__
: «По умолчанию__ne__()
делегирует__eq__()
и инвертирует результат, если это не такNotImplemented
». 2. Если один еще хочет реализовать__ne__
, более общая реализация (один используется Python 3 , я думаю) это:x = self.__eq__(other); if x is NotImplemented: return x; else: return not x
. 3. Данные__eq__
и__ne__
реализации являются неоптимальными:if isinstance(other, type(self)):
дает 22__eq__
и 10__ne__
вызовов, аif isinstance(self, type(other)):
будет 16__eq__
и 6__ne__
вызовов.Вы должны быть осторожны с наследованием:
Проверьте типы более строго, как это:
Кроме того, ваш подход будет работать нормально, для этого есть специальные методы.
источник
NotImplemented
как вы предлагаете, всегда будет причинойsuperclass.__eq__(subclass)
, что является желаемым поведением.if other is self
. Это позволяет избежать более продолжительного сравнения словаря и может значительно сэкономить, когда объекты используются в качестве словарных ключей.__hash__()
То, как вы описываете, это то, как я всегда это делал. Поскольку он полностью универсален, вы всегда можете разбить эту функциональность на класс mixin и наследовать ее в классах, где вы хотите эту функциональность.
источник
other
имеет ли подклассself.__class__
.__dict__
сравнения заключается в том, что если у вас есть атрибут, который вы не хотите учитывать в своем определении равенства (например, уникальный идентификатор объекта или метаданные, такие как метка, созданная во времени).Не прямой ответ, но, казалось, достаточно уместным, чтобы его можно было использовать, так как иногда это спасает от многословной скуки. Вырезать прямо из документов ...
functools.total_ordering (ЦБС)
Учитывая класс, определяющий один или несколько методов упорядочения сравнительного сравнения, этот декоратор класса предоставляет остальное. Это упрощает усилия по определению всех возможных операций расширенного сравнения:
Класс должен определить один из
__lt__()
,__le__()
,__gt__()
или__ge__()
. Кроме того, класс должен предоставить__eq__()
метод.Новое в версии 2.7
источник
Вам не нужно переопределять оба,
__eq__
и__ne__
вы можете переопределить только,__cmp__
но это повлияет на результат ==,! ==, <,> и так далее.is
тесты на предмет идентичности. Это означает, что ais
b будетTrue
в том случае, когда a и b содержат ссылку на один и тот же объект. В python вы всегда держите ссылку на объект в переменной, а не на фактический объект, поэтому, по существу, для того, чтобы a - b было верно, объекты в них должны быть расположены в одной и той же ячейке памяти. Как и самое главное, почему бы вам не изменить это поведение?Изменить: я не знал,
__cmp__
был удален из Python 3, поэтому избегайте его.источник
Из этого ответа: https://stackoverflow.com/a/30676267/541136 Я продемонстрировал это, хотя это правильно определить
__ne__
в терминах__eq__
- вместоВы должны использовать:
источник
Я думаю, что вы ищете два термина: равенство (==) и идентичность (есть). Например:
источник
Тест 'is' проверит идентичность с помощью встроенной функции id (), которая по существу возвращает адрес памяти объекта и, следовательно, не перегружается.
Однако в случае тестирования равенства классов вы, вероятно, захотите быть немного более строгими в своих тестах и сравнивать только атрибуты данных в вашем классе:
Этот код будет сравнивать только не функциональные данные членов вашего класса, а также пропускать что-то приватное, что обычно и требуется. В случае простых старых объектов Python у меня есть базовый класс, который реализует __init__, __str__, __repr__ и __eq__, поэтому мои объекты POPO не несут бремени всей этой дополнительной (и в большинстве случаев идентичной) логики.
источник
__eq__
будет объявлено вCommonEqualityMixin
(см. Другой ответ). Я нашел это особенно полезным при сравнении экземпляров классов, полученных из Base в SQLAlchemy. Чтобы не сравнивать_sa_instance_state
я изменилсяkey.startswith("__")):
наkey.startswith("_")):
. У меня также были некоторые обратные ссылки, и ответ от Алгория вызвал бесконечную рекурсию. Поэтому я назвал все обратные ссылки, начиная с'_'
того, что они также пропускаются при сравнении. ПРИМЕЧАНИЕ: в Python 3.x изменитеiteritems()
наitems()
.__dict__
экземпляр не имеет ничего, что начинается с,__
если это не было определено пользователем. Такие вещи, как__class__
,__init__
и т. Д. Не в экземпляре__dict__
, а в своем классе »__dict__
. OTOH, приватные атрибуты могут легко начаться с__
и, вероятно, должны использоваться для__eq__
. Можете ли вы уточнить, что именно вы пытались избежать при пропуске__
атрибутов с префиксом?Вместо того, чтобы использовать подклассы / миксины, я хотел бы использовать универсальный декоратор класса
Применение:
источник
Это включает в себя комментарии к ответу Алгориаса и сравнивает объекты по одному атрибуту, потому что меня не волнует весь диктат.
hasattr(other, "id")
должно быть верно, но я знаю, что это потому, что я установил его в конструкторе.источник