Python, я должен реализовать оператор __ne__()
на основе __eq__
?
Краткий ответ: Нет. Используйте ==
вместо __eq__
В Python 3 !=
является отрицанием ==
по умолчанию, поэтому вам даже не нужно писать __ne__
, и документация больше не учитывается при написании.
Вообще говоря, для кода только для Python 3 не пишите его, если вам не нужно затмевать родительскую реализацию, например, для встроенного объекта.
То есть, помните Комментарий Раймона Хеттингера :
Метод __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
.
и в разделе «что нового» * 1080 * мы видим, что это поведение изменилось:
!=
теперь возвращает значение, противоположное ==
, если ==
не возвращает 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
в __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
Performance
Не поверьте мне на слово, давайте посмотрим, что более эффективно:
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
раньше был реализацией метода __ne__
по умолчанию в Python 3, но это была ошибка, и она была исправлена в Python 3.4 в январе 2015 года, как заметил ShadowRanger (см. Проблему # 21408).
Хорошо, давайте объясним это.
Как отмечалось ранее, Python 3 по умолчанию обрабатывает __ne__
, сначала проверив, возвращает ли self.__eq__(other)
NotImplemented
(синглтон) - что следует проверить с помощью is
и вернуть, если так, иначе он должен вернуть обратное. Вот эта логика, написанная как класс mixin:
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 API уровня C, и оно было введено в Python 3, делая
избыточный. Все релевантные __ne__
методы были удалены, включая те, которые реализуют свою собственную проверку, а также методы, которые делегируют __eq__
напрямую или через ==
- и ==
был наиболее распространенным способом сделать это.
Заключение
Для Python 2-совместимого кода используйте ==
для реализации __ne__
. Больше:
- правильный
- простой
- производительный
Только в Python 3 используйте низкоуровневое отрицание на уровне C - оно даже больше просто и производительно (хотя программист несет ответственность за определение того, что оно правильно ).
Опять же, не пишите низкоуровневую логику в Python высокого уровня.