Python, должен ли я реализовать __ne __ () Оператор на основе __eq__?
-
08-10-2019 - |
Вопрос
У меня есть класс, где я хочу переопределить __eq__()
оператор. Похоже, имеет смысл, что я должен переопределить __ne__()
оператор, а также имеет смысл реализовать __ne__
на основе __eq__
в качестве таких?
class A:
def __eq__(self, other):
return self.value == other.value
def __ne__(self, other):
return not self.__eq__(other)
Или есть ли что-то, что я скучаю с тем, как Python использует эти операторы, которые делают это не хорошей идеей?
Решение
Да, это совершенно нормально. Фактически, Документация призывает вас определить __ne__
Когда вы определяете __eq__
:
Среди операторов сравнения нет никаких подразумеваемых отношений. Истина
x==y
не подразумевает, чтоx!=y
неверно. Соответственно, при определении__eq__()
, следует также определить__ne__()
Так что операторы будут вести себя как ожидалось.
Во многих случаях (например, этот), это будет так же просто, как отрицание результата __eq__
, но не всегда.
Другие советы
Python, должен ли я реализовать
__ne__()
Оператор на основе__eq__
?
Краткий ответ: Нет. Использование ==
вместо __eq__
В Python 3, !=
это отрицание ==
по умолчанию, чтобы вы даже не обязаны написать __ne__
, и документация больше не является самоуверенностью по написанию одного.
Вообще говоря, для Python 3-Code, не пишу один, если вам не нужно омрачивать родительскую реализацию, например, для встроенного объекта.
То есть имейте в виду Комментарий Raymond Hettinger:
То
__ne__
Метод следует автоматически от__eq__
только если__ne__
еще не определен в суперклассе. Итак, если вы наследуете из встроенного, лучше всего переопределить оба.
Если вам нужен ваш код для работы в Python 2, следуйте рекомендации для Python 2, и он будет работать в Python 3 просто в порядке.
В Python 2 сам Python не реализует никаких операций с точки зрения другого - поэтому вы должны определить __ne__
с точки зрения ==
вместо __eq__
Отказ НАПРИМЕР
class A(object):
def __eq__(self, other):
return self.value == other.value
def __ne__(self, other):
return not self == other # NOT `return not self.__eq__(other)`
Увидеть доказательство этого
- реализация
__ne__()
Оператор на основе__eq__
а также - не реализует
__ne__
в Python 2 вообще
обеспечивает неправильное поведение в демонстрации ниже.
Длительный ответ
То документация Для Python 2 говорит:
Среди операторов сравнения нет никаких подразумеваемых отношений. Истина
x==y
не подразумевает, чтоx!=y
неверно. Соответственно, при определении__eq__()
, следует также определить__ne__()
Так что операторы будут вести себя как ожидалось.
Так что это означает, что если мы определим __ne__
С точки зрения обратного __eq__
, мы можем получить последовательное поведение.
Этот раздел документации был обновлен для Python 3:
По умолчанию,
__ne__()
делегаты к__eq__()
и инвертирует результат, если это неNotImplemented
.
и в "Что нового" раздел, Мы видим, что это поведение изменилось:
!=
теперь возвращает противоположность==
, пока не==
возвращаетсяNotImplemented
.
Для реализации __ne__
, мы предпочитаем использовать ==
оператор вместо того, чтобы использовать __eq__
метод напрямую, так что если self.__eq__(other)
подкласса возвращается NotImplemented
Для проверенного типа Python будет соответственно проверить other.__eq__(self)
Из документации:
То NotImplemented
объект
Этот тип имеет одно значение. С этим значением есть один объект. Этот объект осуществляется через встроенное имя
NotImplemented
. Отказ Числовые методы и богатые методы сравнения могут вернуть это значение, если они не реализуют операцию для предоставленных операндов. (Затем интерпретатор попробует отраженную операцию или какую-то другую неудачу, в зависимости от оператора.) Его значение правды верно.
При условии богатого оператора сравнения, если они не тот же тип, Python проверяет, если other
это подтип, и если он имеет этот оператор, определенный, он использует other
метод сначала (обратно для <
, <=
, >=
а также >
). Если NotImplemented
возвращается, тогда Он использует метод наоборот. (Оно делает нет Проверьте один и тот же метод дважды.) Использование ==
Оператор позволяет иметь эту логику.
Ожидания
Семантически вы должны реализовать __ne__
С точки зрения проверки на равенство, потому что пользователи вашего класса ожидают, что будут эквивалентными следующими функциями для всех случаев.
def negation_of_equals(inst1, inst2):
"""always should return same as not_equals(inst1, inst2)"""
return not inst1 == inst2
def not_equals(inst1, inst2):
"""always should return same as negation_of_equals(inst1, inst2)"""
return inst1 != inst2
То есть оба из приведенных выше функций должны всегда вернуть тот же результат. Но это зависит от программиста.
Демонстрация неожиданного поведения при определении __ne__
на основе __eq__
:
Сначала установка:
class BaseEquatable(object):
def __init__(self, x):
self.x = x
def __eq__(self, other):
return isinstance(other, BaseEquatable) and self.x == other.x
class ComparableWrong(BaseEquatable):
def __ne__(self, other):
return not self.__eq__(other)
class ComparableRight(BaseEquatable):
def __ne__(self, other):
return not self == other
class EqMixin(object):
def __eq__(self, other):
"""override Base __eq__ & bounce to other for __eq__, e.g.
if issubclass(type(self), type(other)): # True in this example
"""
return NotImplemented
class ChildComparableWrong(EqMixin, ComparableWrong):
"""__ne__ the wrong way (__eq__ directly)"""
class ChildComparableRight(EqMixin, ComparableRight):
"""__ne__ the right way (uses ==)"""
class ChildComparablePy3(EqMixin, BaseEquatable):
"""No __ne__, only right in Python 3."""
Значение неэквивалентных случаев:
right1, right2 = ComparableRight(1), ChildComparableRight(2)
wrong1, wrong2 = ComparableWrong(1), ChildComparableWrong(2)
right_py3_1, right_py3_2 = BaseEquatable(1), ChildComparablePy3(2)
Ожидаемое поведение:
(Примечание. В то время как каждое второе утверждение каждого из нижеперечисленного является эквивалентным и, следовательно, логически избыточным до того, как до него, я включаю их, чтобы продемонстрировать, что Заказ не имеет значения, когда один подкласс другой.)
Эти экземпляры есть __ne__
реализован с ==
:
assert not right1 == right2
assert not right2 == right1
assert right1 != right2
assert right2 != right1
Эти экземпляры, тестирование под Python 3, также работают правильно:
assert not right_py3_1 == right_py3_2
assert not right_py3_2 == right_py3_1
assert right_py3_1 != right_py3_2
assert right_py3_2 != right_py3_1
И напомнить, что они имеют __ne__
реализован с __eq__
- Хотя это ожидаемое поведение, реализация неверна:
assert not wrong1 == wrong2 # These are contradicted by the
assert not wrong2 == wrong1 # below unexpected behavior!
Неожиданное поведение:
Обратите внимание, что это сравнение противоречит сравнению выше (not wrong1 == wrong2
).
>>> assert wrong1 != wrong2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError
а также,
>>> assert wrong2 != wrong1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError
Не пропустите __ne__
в Python 2.
Для доказательств того, что вы не должны пропускать реализацию __ne__
В Python 2 см. Эти эквивалентные объекты:
>>> right_py3_1, right_py3_1child = BaseEquatable(1), ChildComparablePy3(1)
>>> right_py3_1 != right_py3_1child # as evaluated in Python 2!
True
Вышеуказанный результат должен быть False
!
Python 3 Source.
Реализация CPYON по умолчанию для __ne__
в typeobject.c
в object_richcompare
:
case Py_NE:
/* By default, __ne__() delegates to __eq__() and inverts the result,
unless the latter returns NotImplemented. */
if (self->ob_type->tp_richcompare == NULL) {
res = Py_NotImplemented;
Py_INCREF(res);
break;
}
res = (*self->ob_type->tp_richcompare)(self, other, Py_EQ);
if (res != NULL && res != Py_NotImplemented) {
int ok = PyObject_IsTrue(res);
Py_DECREF(res);
if (ok < 0)
res = NULL;
else {
if (ok)
res = Py_False;
else
res = Py_True;
Py_INCREF(res);
}
}
Здесь мы видим
Но по умолчанию __ne__
использует __eq__
?
Python 3 по умолчанию __ne__
Деталь внедрения на уровне C используются __eq__
потому что более высокий уровень ==
(Pyobject_richcompare.) будет менее эффективным - и поэтому он должен также обращаться NotImplemented
.
Если __eq__
правильно реализован, то отрицание ==
также правильно - и это позволяет нам избежать низкоуровневых деталей реализации в наших __ne__
.
С использованием ==
позволяет нам сохранить нашу логику низкого уровня в один Место и избегать адресация NotImplemented
в __ne__
.
Может неверно предположить, что ==
может вернуться NotImplemented
.
На самом деле он использует ту же логику, что и реализация по умолчанию __eq__
, который проверяет идентичность (см. do_richcompare. и наши доказательства ниже)
class Foo:
def __ne__(self, other):
return NotImplemented
__eq__ = __ne__
f = Foo()
f2 = Foo()
И сравнения:
>>> f == f
True
>>> f != f
False
>>> f2 == f
False
>>> f2 != f
True
Представление
Не принимайте мое слово для этого, давайте посмотрим, что более исполнительно:
class CLevel:
"Use default logic programmed in C"
class HighLevelPython:
def __ne__(self, other):
return not self == other
class LowLevelPython:
def __ne__(self, other):
equal = self.__eq__(other)
if equal is NotImplemented:
return NotImplemented
return not equal
def c_level():
cl = CLevel()
return lambda: cl != cl
def high_level_python():
hlp = HighLevelPython()
return lambda: hlp != hlp
def low_level_python():
llp = LowLevelPython()
return lambda: llp != llp
Я думаю, что эти номера производительности говорят сами по себе:
>>> import timeit
>>> min(timeit.repeat(c_level()))
0.09377292497083545
>>> min(timeit.repeat(high_level_python()))
0.2654011140111834
>>> min(timeit.repeat(low_level_python()))
0.3378178110579029
Это имеет смысл, когда вы считаете, что low_level_python
делает логику в Python, которая в противном случае будет обрабатываться на уровне C.
Ответ на некоторые критики
Другой ответчик пишет:
Реализация зала Аарона
not self == other
принадлежащий__ne__
Метод неверный, как никогда не может вернутьсяNotImplemented
(not NotImplemented
являетсяFalse
) и поэтому__ne__
метод, который имеет приоритет, может никогда не вернуться на__ne__
Метод, который не имеет приоритета.
Иметь __ne__
Никогда не возвращайся NotImplemented
не делает это неверным. Вместо этого мы обращаемся к приоритету с NotImplemented
через проверку на равенство с ==
. Отказ Предположить ==
правильно реализован, мы закончили.
not self == other
используется для реализации Python по умолчанию Python 3__ne__
Метод, но это был ошибкой, и он был исправлен в Python 3.4 января 2015 года, как заметил Тенерендр (см. Выпуск № 21408).
Ну, давайте объясним это.
Как отмечалось ранее, Python 3 по умолчанию ручки __ne__
сначала проверяя, если self.__eq__(other)
возвращается NotImplemented
(Singleton) - который следует проверить с is
и вернулся, если да, то еще оно должно вернуть обратное. Вот эта логика, написанная как класс микс:
class CStyle__ne__:
"""Mixin that provides __ne__ functionality equivalent to
the builtin functionality
"""
def __ne__(self, other):
equal = self.__eq__(other)
if equal is NotImplemented:
return NotImplemented
return not equal
Это необходимо для правильности для Python Python API, и он был введен в Python 3, что делает
- то
__ne__
Методы в этом патче закрыть Выпуск 21408. а также - то
__ne__
Методы в последующей очистке удалены здесь
избыточный. Все актуально __ne__
методы были удалены, в том числе те, которые реализуют их собственный чек, а также те, которые делегат __eq__
напрямую или через ==
- а также ==
был самым распространенным способом сделать это.
Заключение
Для Python 2 совместимого кода, используйте ==
реализовать __ne__
. Отказ Это больше:
- правильный
- просто
- исполнитель
В Python 3 только используйте отрицание низкого уровня на уровне C - это даже более простым и исполнительным (хотя программист отвечает за определение того, что это правильный).
Снова сделать нет Напишите логику низкого уровня в Python высокого уровня.
Просто для записи, канонически правильный и перекрестный PY2 / PY3 портативный __ne__
будет выглядеть так:
import sys
class ...:
...
def __eq__(self, other):
...
if sys.version_info[0] == 2:
def __ne__(self, other):
equal = self.__eq__(other)
return equal if equal is NotImplemented else not equal
Это работает с любым __eq__
Вы можете определить:
- В отличие от
not (self == other)
, не вмешивается в некоторые раздражающие / сложные случаи, связанные с сравнениями, где один из участийся классов не означает, что результат__ne__
такой же, как результатnot
на__eq__
(например, ORM SQLALCHEMY, где оба__eq__
а также__ne__
Вернуть специальные прокси-объекты, а неTrue
илиFalse
, и пытаясьnot
результат__eq__
вернетсяFalse
, а не правильный прокси-объект). - В отличие от
not self.__eq__(other)
, это правильно делегаты к__ne__
другого случая, когдаself.__eq__
возвращаетсяNotImplemented
(not self.__eq__(other)
будет лишнее неправильно, потому чтоNotImplemented
это правда, так когда__eq__
не знал, как выполнить сравнение,__ne__
вернетсяFalse
, подразумевая, что два объекта были равны, когда на самом деле единственный задающий объект понятия не имел, что подразумевает бы по умолчанию не равным)
Если ваш __eq__
не использует NotImplemented
Возвращает, это работает (с бессмысленным накладным расходом), если он использует NotImplemented
Иногда это правильно обработает его. И проверка версии Python означает, что если класс import
- в Python 3, __ne__
остается неопределенным, что позволит родной, эффективный ответ Python __ne__
Реализация (версия C вышесказанного) взять на себя.
Почему это нужно
Правила перегрузки Python
Объяснение того, почему вы делаете это вместо других решений, несколько тайны. Python имеет пару общих правил об перегрузках операторов и операторам сравнения:
- (Относится ко всем операторам) при запуске
LHS OP RHS
, пытатьсяLHS.__op__(RHS)
, и если это возвращаетсяNotImplemented
, пытатьсяRHS.__rop__(LHS)
. Отказ Исключение: еслиRHS
это подклассLHS
класс, затем тестRHS.__rop__(LHS)
первый. Отказ В случае сравнения операторов,__eq__
а также__ne__
являются их собственными «ROP» (поэтому тестовый порядок для__ne__
являетсяLHS.__ne__(RHS)
, тогдаRHS.__ne__(LHS)
, изменено, еслиRHS
это подклассLHS
класс) - Помимо идеи «поменянного» оператора, между операторами нет никаких подразумеваемых отношений. Даже например одного класса,
LHS.__eq__(RHS)
возвращениеTrue
не подразумеваетLHS.__ne__(RHS)
возвращаетсяFalse
(Фактически, операторы даже не требуются для возврата логических ценностей; ORMS, такие как SQLALCHEMY намеренно, не позволяя более выразительным синтаксисом запроса). Как на Python 3, по умолчанию__ne__
Реализация ведет себя таким образом, но это не договорное; Вы можете переопределить__ne__
таким образом, что не строгие противоположности__eq__
.
Как это относится к перегрузке компараторов
Поэтому, когда вы перегружаете оператора, у вас есть две работы:
- Если вы знаете, как реализовать операцию самостоятельно, сделайте это, используя Только Ваши собственные знания о том, как сделать сравнение (никогда не делегируйте, неявно или явно или явно, на другую сторону операции; делать это, рискует неверность и / или бесконечное рекурсию, в зависимости от того, как вы это делаете)
- если ты нет знать, как реализовать операцию самостоятельно, всегда вернуть
NotImplemented
, поэтому Python может делегировать к реализации другого операнда
Проблема с not self.__eq__(other)
def __ne__(self, other):
return not self.__eq__(other)
никогда не делегирует на другую сторону (и неверно, если __eq__
правильно возвращает NotImplemented
). Когда self.__eq__(other)
возвращается NotImplemented
(который является «правдой»), вы молча вернусь False
, так A() != something_A_knows_nothing_about
возвращается False
, когда он должен был проверить, если something_A_knows_nothing_about
знал, как сравнить с экземплярами A
, и если это не так, это должно было вернуть True
(Поскольку, поскольку ни одна из сторон не знает, как сравнивать с другой, они считаются не равными друг другу). Если A.__eq__
неверно реализован (возвращается False
вместо NotImplemented
Когда он не распознает другую сторону), то это «правильно» от A
Перспектива, возвращаясь True
(поскольку A
не думает, что это равно, так что это не равно), но это может быть не так от something_A_knows_nothing_about
Перспектива, так как он даже не спросил something_A_knows_nothing_about
; A() != something_A_knows_nothing_about
оказывается True
, но something_A_knows_nothing_about != A()
мог False
, или любое другое возвращаемое значение.
Проблема с not self == other
def __ne__(self, other):
return not self == other
более тонкий. Это будет правильно на 99% классов, включая все классы, для которых __ne__
логическая инверсия __eq__
. Отказ Но not self == other
ломает оба правила, упомянутые выше, что означает для классов, где __ne__
нет логический обратный __eq__
, результаты еще раз не рефлексивны, потому что один из операндов никогда не спрашивает, может ли она реализовать __ne__
Вообще, даже если другой операнд не может. Самый простой пример - класс Wirdo, который возвращает False
для все Сравнение, так A() == Incomparable()
а также A() != Incomparable()
оба возврата False
. Отказ С правильной реализацией A.__ne__
(тот, который возвращает NotImplemented
Когда он не знает, как сделать сравнение), отношения рефлексивны; A() != Incomparable()
а также Incomparable() != A()
договориться о результате (потому что в прежнем случае, A.__ne__
возвращается NotImplemented
, тогда Incomparable.__ne__
возвращается False
, пока во второй Incomparable.__ne__
возвращается False
напрямую). Но когда A.__ne__
реализован как return not self == other
, A() != Incomparable()
возвращается True
(потому что A.__eq__
Возвращает, нет NotImplemented
, тогда Incomparable.__eq__
возвращается False
, а также A.__ne__
инверты, что True
), пока Incomparable() != A()
возвращается False.
Вы можете увидеть пример этого в действии здесь.
Очевидно, класс, который всегда возвращается False
для обоих __eq__
а также __ne__
немного странно. Но как упоминалось ранее, __eq__
а также __ne__
даже не нужно возвращаться True
/False
; SQLALCHEMY ORM имеет классы с компараторами, которые возвращают специальный прокси-объект для застройки запроса, а не True
/False
Вообще (они «правда», если они оцениваются в булевом контексте, но они никогда не должны оцениваться в таком контексте).
Неспособность перегружать __ne__
Правильно, ты буду Перерыв классов такого рода, как код:
results = session.query(MyTable).filter(MyTable.fieldname != MyClassWithBadNE())
будет работать (предполагая, что sqlalchemy знает, как вставить MyClassWithBadNE
в SQL String вообще; Это можно сделать с помощью адаптеров типа без MyClassWithBadNE
необходимо вообще сотрудничать), передавая ожидаемый прокси-объект к filter
, пока:
results = session.query(MyTable).filter(MyClassWithBadNE() != MyTable.fieldname)
закончится прохождение filter
равнина False
, потому что self == other
Возвращает прокси-объект и not self == other
просто преобразует правдоподобный объект прокси False
. Отказ С надеждой, filter
бросает исключение на обработку неверных аргументов, как False
. Отказ Пока я уверен, что многие утверждают, что MyTable.fieldname
должен Будьте последовательно на левой стороне сравнения, факт остается фактом, что нет программной причины для обеспечения того, чтобы обеспечить это в общем случае, и правильный универсальный __ne__
будет работать в любом случае, пока return not self == other
работает только в одном договоренности.
Краткий ответ: Да (но прочитайте документацию, чтобы сделать это правильно)
Реализация Shadowranger __ne__
Метод является правильным (в том смысле, что он ведет себя точно так же, как реализация Python Python по умолчанию):
def __ne__(self, other):
result = self.__eq__(other)
if result is not NotImplemented:
return not result
return NotImplemented
Реализация зала Аарона not self == other
принадлежащий __ne__
Метод неверный, как никогда не может вернуться NotImplemented
(not NotImplemented
является False
) и поэтому __ne__
метод, который имеет приоритет, может никогда не вернуться на __ne__
Метод, который не имеет приоритета. not self == other
используется для реализации Python по умолчанию Python 3 __ne__
метод, но это был ошибкой, и он был исправлен в Python 3.4 января 2015 года, как заметил Shadowranger (см. Выпуск # 21408.).
Реализация операторов сравнения
То Справочник языка Python для Python 3 государства в его Глава III Модель данных:
object.__lt__(self, other)
object.__le__(self, other)
object.__eq__(self, other)
object.__ne__(self, other)
object.__gt__(self, other)
object.__ge__(self, other)
Это так называемые методы «богатых сравнений». Переписка между символами оператора и именами методов заключается в следующем:
x<y
вызовыx.__lt__(y)
,x<=y
вызовыx.__le__(y)
,x==y
вызовыx.__eq__(y)
,x!=y
вызовыx.__ne__(y)
,x>y
вызовыx.__gt__(y)
, а такжеx>=y
вызовыx.__ge__(y)
.Богатый способ сравнения может вернуть синглтон
NotImplemented
Если он не реализует операцию для данной пары аргументов.Нет переключаемых версий аргументов этих методов (для использования, когда левый аргумент не поддерживает операцию, но имеет правильный аргумент); скорее,
__lt__()
а также__gt__()
Отражение друг друга,__le__()
а также__ge__()
Отражение друг друга, и__eq__()
а также__ne__()
их собственное отражение. Если операнды имеют разные типы, а тип правого операнда является прямым или косвенным подклассом типа левого операнда, отраженный метод правого операнда имеет приоритет, в противном случае левый операнд имеет приоритет. Виртуальные подклассы не рассматриваются.
Перевод этого в код Python дает (используя operator_eq
для ==
, operator_ne
для !=
, operator_lt
для <
, operator_gt
для >
, operator_le
для <=
а также operator_ge
для >=
):
def operator_eq(left, right):
if type(left) != type(right) and isinstance(right, type(left)):
result = right.__eq__(left)
if result is NotImplemented:
result = left.__eq__(right)
else:
result = left.__eq__(right)
if result is NotImplemented:
result = right.__eq__(left)
if result is NotImplemented:
result = left is right
return result
def operator_ne(left, right):
if type(left) != type(right) and isinstance(right, type(left)):
result = right.__ne__(left)
if result is NotImplemented:
result = left.__ne__(right)
else:
result = left.__ne__(right)
if result is NotImplemented:
result = right.__ne__(left)
if result is NotImplemented:
result = left is not right
return result
def operator_lt(left, right):
if type(left) != type(right) and isinstance(right, type(left)):
result = right.__gt__(left)
if result is NotImplemented:
result = left.__lt__(right)
else:
result = left.__lt__(right)
if result is NotImplemented:
result = right.__gt__(left)
if result is NotImplemented:
raise TypeError(f"'<' not supported between instances of '{type(left).__name__}' and '{type(right).__name__}'")
return result
def operator_gt(left, right):
if type(left) != type(right) and isinstance(right, type(left)):
result = right.__lt__(left)
if result is NotImplemented:
result = left.__gt__(right)
else:
result = left.__gt__(right)
if result is NotImplemented:
result = right.__lt__(left)
if result is NotImplemented:
raise TypeError(f"'>' not supported between instances of '{type(left).__name__}' and '{type(right).__name__}'")
return result
def operator_le(left, right):
if type(left) != type(right) and isinstance(right, type(left)):
result = right.__ge__(left)
if result is NotImplemented:
result = left.__le__(right)
else:
result = left.__le__(right)
if result is NotImplemented:
result = right.__ge__(left)
if result is NotImplemented:
raise TypeError(f"'<=' not supported between instances of '{type(left).__name__}' and '{type(right).__name__}'")
return result
def operator_ge(left, right):
if type(left) != type(right) and isinstance(right, type(left)):
result = right.__le__(left)
if result is NotImplemented:
result = left.__ge__(right)
else:
result = left.__ge__(right)
if result is NotImplemented:
result = right.__le__(left)
if result is NotImplemented:
raise TypeError(f"'>=' not supported between instances of '{type(left).__name__}' and '{type(right).__name__}'")
return result
Реализация по умолчанию методов сравнения
Документация добавляет:
По умолчанию,
__ne__()
делегаты к__eq__()
и инвертирует результат, если это неNotImplemented
. Отказ Других подразумеваемых отношений между операторами сравнения, например, правда(x<y or x==y)
не подразумеваетx<=y
.
Реализация по умолчанию методов сравнения (__eq__
, __ne__
, __lt__
, __gt__
, __le__
а также __ge__
) может таким образом быть дан:
def __eq__(self, other):
return NotImplemented
def __ne__(self, other):
result = self.__eq__(other)
if result is not NotImplemented:
return not result
return NotImplemented
def __lt__(self, other):
return NotImplemented
def __gt__(self, other):
return NotImplemented
def __le__(self, other):
return NotImplemented
def __ge__(self, other):
return NotImplemented
Так что это правильная реализация __ne__
метод. И это не всегда возвращает обратный __eq__
метод, потому что когда __eq__
Метод возвращает NotImplemented
, его обратное not NotImplemented
является False
(так как bool(NotImplemented)
является True
) вместо желаемого NotImplemented
.
Неправильные реализации __ne__
Как продемонстрировано Аарон Холл выше, not self.__eq__(other)
не является реализацией по умолчанию __ne__
метод. Но ни это not self == other
. Последнее демонстрируется ниже, сравнивая поведение реализации по умолчанию с поведением not self == other
Реализация в двух случаях:
- то
__eq__
Метод возвращаетNotImplemented
; - то
__eq__
Метод возвращает значение, отличное отNotImplemented
.
Реализация по умолчанию
Давайте посмотрим, что произойдет, когда A.__ne__
Метод использует реализацию по умолчанию и A.__eq__
Метод возвращает NotImplemented
:
class A:
pass
class B:
def __ne__(self, other):
return "B.__ne__"
assert (A() != B()) == "B.__ne__"
!=
вызовыA.__ne__
.A.__ne__
вызовыA.__eq__
.A.__eq__
возвращаетсяNotImplemented
.!=
вызовыB.__ne__
.B.__ne__
возвращается"B.__ne__"
.
Это показывает, что когда A.__eq__
Метод возвращает NotImplemented
, то A.__ne__
Метод отступает на B.__ne__
метод.
Теперь давайте посмотрим, что произойдет, когда A.__ne__
Метод использует реализацию по умолчанию и A.__eq__
Метод возвращает значение, отличное от NotImplemented
:
class A:
def __eq__(self, other):
return True
class B:
def __ne__(self, other):
return "B.__ne__"
assert (A() != B()) is False
!=
вызовыA.__ne__
.A.__ne__
вызовыA.__eq__
.A.__eq__
возвращаетсяTrue
.!=
возвращаетсяnot True
, то естьFalse
.
Это показывает, что в этом случае A.__ne__
Метод возвращает обратную A.__eq__
метод. Таким образом __ne__
Метод ведет себя как рекламируемый в документации.
Переопределение реализации по умолчанию A.__ne__
Способ с правильной реализацией приведенный выше, дает те же результаты.
not self == other
реализация
Давайте посмотрим, что произойдет при переопределении реализации по умолчанию A.__ne__
метод с not self == other
Реализация и то A.__eq__
Метод возвращает NotImplemented
:
class A:
def __ne__(self, other):
return not self == other
class B:
def __ne__(self, other):
return "B.__ne__"
assert (A() != B()) is True
!=
вызовыA.__ne__
.A.__ne__
вызовы==
.==
вызовыA.__eq__
.A.__eq__
возвращаетсяNotImplemented
.==
вызовыB.__eq__
.B.__eq__
возвращаетсяNotImplemented
.==
возвращаетсяA() is B()
, то естьFalse
.A.__ne__
возвращаетсяnot False
, то естьTrue
.
Реализация по умолчанию __ne__
Метод возвращен "B.__ne__"
, нет True
.
Теперь давайте посмотрим, что произойдет при переопределении реализации по умолчанию A.__ne__
метод с not self == other
Реализация и то A.__eq__
Метод возвращает значение, отличное от NotImplemented
:
class A:
def __eq__(self, other):
return True
def __ne__(self, other):
return not self == other
class B:
def __ne__(self, other):
return "B.__ne__"
assert (A() != B()) is False
!=
вызовыA.__ne__
.A.__ne__
вызовы==
.==
вызовыA.__eq__
.A.__eq__
возвращаетсяTrue
.A.__ne__
возвращаетсяnot True
, то естьFalse
.
Реализация по умолчанию __ne__
Метод также вернулся False
в этом случае.
Поскольку эта реализация не может повторить поведение реализации по умолчанию __ne__
метод, когда то __eq__
Метод возвращает NotImplemented
, это неверно.
Если все __eq__
, __ne__
, __lt__
, __ge__
, __le__
, а также __gt__
иметь смысл для класса, то просто реализуйте __cmp__
вместо. В противном случае, делайте, как вы делаете, из-за бита Даниэль Дипаооло сказал (пока я проверял его вместо того, чтобы смотреть это;))