В 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, это неверно.