В Python, я должен реализовать нэ (оператором) производится по формулам?
У меня есть класс, в котором я хочу переопределить оператор __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 использует эти операторы, что делает это не очень хорошей идеей?
5 ответов:
Да, это совершенно нормально. Фактически, документация призывает вас определить
__ne__
, когда вы определяете__eq__
:Нет никаких подразумеваемых отношений среди операторов сравнения. То истинность
x==y
не означает, чтоx!=y
ложный. Соответственно, при определении__eq__()
, Следует также определить__ne__()
так, чтобы операторы вели себя ожидаемым образом.Во многих случаях (таких как этот), это будет так же просто, как отрицание результата
__eq__
, но не всегда.
Python, должен ли я реализовать оператор
__ne__()
на основе__eq__
?Короткий Ответ: Нет. Используйте
==
вместо__eq__
В Python 3,
!=
является отрицанием==
по умолчанию, так что вы даже не обязаны писать__ne__
, и документация больше не настаивает на написании одного.Вообще говоря, для кода Python 3-only не пишите его, если вам не нужно затмить родительскую реализацию, например, для встроенного кода. объект.
То есть имейте в виду комментарий Раймонда Хеттингера :
Метод
__ne__
автоматически следует из__eq__
только в том случае, если__ne__
еще не определен в суперклассе. Так что, если ты ... наследуя от встроенного, лучше всего переопределить оба.Если вам нужно, чтобы ваш код работал в Python 2, следуйте рекомендациям для Python 2, и он будет работать в Python 3 просто отлично.
В Python 2 сам Python автоматически не работает. реализуйте любую операцию в терминах другой-следовательно, вы должны определить
__ne__
в терминах==
вместо__eq__
. E. G.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__
в терминах проверки на равенство, потому что пользователи вашего класса будут ожидать, что следующие функции будут эквивалентны для всех экземпляров A.:То есть обе вышеперечисленные функции должны всегда возвращать один и тот же результат. Но это зависит от программиста.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!
Неожиданное Поведение:
Обратите внимание, что это сравнение противоречит приведенным выше сравнениям ().
>>> 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
Реализация CPython по умолчанию для
__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.Заключение
Для Python 2 совместимо код, используйте
==
для реализации__ne__
. Это больше:
- правильно
- просто
- исполнитель
Только в Python 3 Используйте низкоуровневое отрицание на уровне C-оно даже более простое и производительное (хотя программист отвечает за определение того, что это правильно).
Делать не написать низкоуровневую логику питона в высоком уровне.
Просто для записи, канонически правильный и перекрестный 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)
, не вмешивается в некоторые раздражающие/сложные случаи, включающие сравнения между экземплярами, где один экземпляр относится к подклассу другого. Если ваш__eq__
не используетNotImplemented
возвраты, это работает (с бессмысленными накладными расходами), если он используетNotImplemented
иногда, это обрабатывает его должным образом. А проверка версии Python означает что если классimport
- ed в Python 3,__ne__
остается неопределенным, что позволяет использовать собственный, эффективный запасной вариант Python__ne__
реализация (версия C выше) , чтобы взять на себя.
Если все
__eq__
,__ne__
,__lt__
,__ge__
,__le__
, и__gt__
имеют смысл для класса, а затем просто реализуют__cmp__
вместо этого. В противном случае, делайте то, что вы делаете, из-за бита, который сказал Даниэль Дипаоло (в то время как я тестировал его, а не искал его;))
Краткий ответ: да (но прочитайте документацию, чтобы сделать это правильно)
Хотя и интересно, ответ Аарона холла не является правильным способом реализации метода
__ne__
, потому что при реализацииnot self == other
метод__ne__
другого операнда никогда не рассматривается. В отличие от этого, как показано ниже, реализация Python 3 по умолчанию метода__ne__
операнда делает резервную копию метода__ne__
другого операнда, возвращаяNotImplemented
, когда его метод__eq__
возвращаетNotImplemented
. ShadowRanger дал правильную реализацию метода__ne__
:def __ne__(self, other): result = self.__eq__(other) if result is not NotImplemented: return not result return NotImplemented
Реализация операторов сравнения
В на 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 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 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 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 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 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 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
, это неверно.