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
, это неправильно.