Python, должен ли я реализовать оператор ne () на основе eq?
у меня есть класс, где я хочу, чтобы переопределить __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 не пишите один, если вам не нужно затмить родителя реализация, например, для встроенного объекта.
то есть, имейте в виду компания Raymond Hettinger это:
на
__ne__метод следует автоматически из__eq__только если__ne__еще не определен в суперклассе. Так что, если вы наследуя от builtin, лучше всего переопределить оба.
Если вам нужен ваш код для работы в 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__ С точки зрения проверки на равенство поскольку пользователи вашего класса ожидают, что следующие функции будут эквивалентны для всех экземпляров 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!
Неожиданное Поведение:
обратите внимание, что это сравнение противоречит приведенным выше сравнениям (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
реализация 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 in __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-это даже больше простой и работоспособный (хотя программист отвечает за определение того, что это правильно).
Do не запись логики низкого уровня в 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), не вмешивается в некоторые раздражающие/сложные случаи, связанные с сравнениями между экземплярами, где один экземпляр относится к подклассу другого. Если __eq__ не использовать NotImplemented возвращает, это работает (с бессмысленными накладными расходами), если он использует NotImplemented иногда, это обрабатывает его должным образом. И Проверка версии Python означает, что если класс import - ed в Python 3,__ne__ остается неопределенным, позволяя родной, эффективный резерв Python __ne__ реализация (версия C выше) взять на себя.
если все __eq__, __ne__, __lt__, __ge__, __le__ и __gt__ имеет смысл для класса, а затем просто реализовать . В противном случае делайте, как вы делаете, из-за бит Даниэль Дипаоло сказал (в то время как я тестировал его вместо того, чтобы искать его ;) )
короткий ответ: да (но читаю документацию, чтобы сделать это правильно)
хотя интересно, ответ Аарона холла не является правильным способом реализации __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если он не реализует операцию для данной пары аргументов.там не являются ли версии этих методов swapped-argument (для использования когда левый аргумент не поддерживает операцию, а правый аргумент делает); скорее,
__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 is False (as bool(NotImplemented) и True) вместо желаемого NotImplemented.
неправильные реализации __ne__
как показал Аарон Холл выше,not self.__eq__(other) не является правильной реализацией __ne__ метод. а не not self == other. последнее продемонстрировано ниже путем сравнивать поведение реализация по умолчанию с поведением not self == other реализация в двух случаях:
- the
__eq__возвращаетNotImplemented; - the
__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, the 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, это неправильно.