Python, должен ли я реализовать __ne __ () Оператор на основе __eq__?

StackOverflow https://stackoverflow.com/questions/4352244

Вопрос

У меня есть класс, где я хочу переопределить __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 использует эти операторы, которые делают это не хорошей идеей?

Это было полезно?

Решение

Да, это совершенно нормально. Фактически, Документация призывает вас определить __ne__ Когда вы определяете __eq__:

Среди операторов сравнения нет никаких подразумеваемых отношений. Истина x==y не подразумевает, что x!=yневерно. Соответственно, при определении __eq__(), следует также определить __ne__() Так что операторы будут вести себя как ожидалось.

Во многих случаях (например, этот), это будет так же просто, как отрицание результата __eq__, но не всегда.

Другие советы

Python, должен ли я реализовать __ne__() Оператор на основе __eq__?

Краткий ответ: Нет. Использование == вместо __eq__

В Python 3, != это отрицание == по умолчанию, чтобы вы даже не обязаны написать __ne__, и документация больше не является самоуверенностью по написанию одного.

Вообще говоря, для Python 3-Code, не пишу один, если вам не нужно омрачивать родительскую реализацию, например, для встроенного объекта.

То есть имейте в виду Комментарий Raymond Hettinger:

То __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.

и в "Что нового" раздел, Мы видим, что это поведение изменилось:

  • != теперь возвращает противоположность ==, пока не == возвращается NotImplemented.

Для реализации __ne__, мы предпочитаем использовать == оператор вместо того, чтобы использовать __eq__ метод напрямую, так что если self.__eq__(other) подкласса возвращается NotImplemented Для проверенного типа Python будет соответственно проверить other.__eq__(self) Из документации:

То NotImplemented объект

Этот тип имеет одно значение. С этим значением есть один объект. Этот объект осуществляется через встроенное имя NotImplemented. Отказ Числовые методы и богатые методы сравнения могут вернуть это значение, если они не реализуют операцию для предоставленных операндов. (Затем интерпретатор попробует отраженную операцию или какую-то другую неудачу, в зависимости от оператора.) Его значение правды верно.

При условии богатого оператора сравнения, если они не тот же тип, Python проверяет, если other это подтип, и если он имеет этот оператор, определенный, он использует otherметод сначала (обратно для <, <=, >= а также >). Если NotImplemented возвращается, тогда Он использует метод наоборот. (Оно делает нет Проверьте один и тот же метод дважды.) Использование == Оператор позволяет иметь эту логику.


Ожидания

Семантически вы должны реализовать __ne__ С точки зрения проверки на равенство, потому что пользователи вашего класса ожидают, что будут эквивалентными следующими функциями для всех случаев.

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.

Реализация CPYON по умолчанию для __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.

Ответ на некоторые критики

Другой ответчик пишет:

Реализация зала Аарона not self == other принадлежащий __ne__ Метод неверный, как никогда не может вернуться NotImplemented (not NotImplemented является False) и поэтому __ne__ метод, который имеет приоритет, может никогда не вернуться на __ne__ Метод, который не имеет приоритета.

Иметь __ne__ Никогда не возвращайся NotImplemented не делает это неверным. Вместо этого мы обращаемся к приоритету с NotImplemented через проверку на равенство с ==. Отказ Предположить == правильно реализован, мы закончили.

not self == other используется для реализации Python по умолчанию Python 3 __ne__ Метод, но это был ошибкой, и он был исправлен в Python 3.4 января 2015 года, как заметил Тенерендр (см. Выпуск № 21408).

Ну, давайте объясним это.

Как отмечалось ранее, Python 3 по умолчанию ручки __ne__ сначала проверяя, если self.__eq__(other) возвращается NotImplemented (Singleton) - который следует проверить с is и вернулся, если да, то еще оно должно вернуть обратное. Вот эта логика, написанная как класс микс:

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 Python API, и он был введен в Python 3, что делает

избыточный. Все актуально __ne__ методы были удалены, в том числе те, которые реализуют их собственный чек, а также те, которые делегат __eq__ напрямую или через == - а также == был самым распространенным способом сделать это.

Заключение

Для Python 2 совместимого кода, используйте == реализовать __ne__. Отказ Это больше:

  • правильный
  • просто
  • исполнитель

В Python 3 только используйте отрицание низкого уровня на уровне C - это даже более простым и исполнительным (хотя программист отвечает за определение того, что это правильный).

Снова сделать нет Напишите логику низкого уровня в 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), не вмешивается в некоторые раздражающие / сложные случаи, связанные с сравнениями, где один из участийся классов не означает, что результат __ne__ такой же, как результат not на __eq__ (например, ORM SQLALCHEMY, где оба __eq__ а также __ne__ Вернуть специальные прокси-объекты, а не True или False, и пытаясь not результат __eq__ вернется False, а не правильный прокси-объект).
  • В отличие от not self.__eq__(other), это правильно делегаты к __ne__ другого случая, когда self.__eq__ возвращается NotImplemented (not self.__eq__(other) будет лишнее неправильно, потому что NotImplemented это правда, так когда __eq__ не знал, как выполнить сравнение, __ne__ вернется False, подразумевая, что два объекта были равны, когда на самом деле единственный задающий объект понятия не имел, что подразумевает бы по умолчанию не равным)

Если ваш __eq__ не использует NotImplemented Возвращает, это работает (с бессмысленным накладным расходом), если он использует NotImplemented Иногда это правильно обработает его. И проверка версии Python означает, что если класс import- в Python 3, __ne__ остается неопределенным, что позволит родной, эффективный ответ Python __ne__ Реализация (версия C вышесказанного) взять на себя.


Почему это нужно

Правила перегрузки Python

Объяснение того, почему вы делаете это вместо других решений, несколько тайны. Python имеет пару общих правил об перегрузках операторов и операторам сравнения:

  1. (Относится ко всем операторам) при запуске LHS OP RHS, пытаться LHS.__op__(RHS), и если это возвращается NotImplemented, пытаться RHS.__rop__(LHS). Отказ Исключение: если RHS это подкласс LHSкласс, затем тест RHS.__rop__(LHS) первый. Отказ В случае сравнения операторов, __eq__ а также __ne__ являются их собственными «ROP» (поэтому тестовый порядок для __ne__ является LHS.__ne__(RHS), тогда RHS.__ne__(LHS), изменено, если RHS это подкласс LHSкласс)
  2. Помимо идеи «поменянного» оператора, между операторами нет никаких подразумеваемых отношений. Даже например одного класса, LHS.__eq__(RHS) возвращение True не подразумевает LHS.__ne__(RHS) возвращается False (Фактически, операторы даже не требуются для возврата логических ценностей; ORMS, такие как SQLALCHEMY намеренно, не позволяя более выразительным синтаксисом запроса). Как на Python 3, по умолчанию __ne__ Реализация ведет себя таким образом, но это не договорное; Вы можете переопределить __ne__ таким образом, что не строгие противоположности __eq__.

Как это относится к перегрузке компараторов

Поэтому, когда вы перегружаете оператора, у вас есть две работы:

  1. Если вы знаете, как реализовать операцию самостоятельно, сделайте это, используя Только Ваши собственные знания о том, как сделать сравнение (никогда не делегируйте, неявно или явно или явно, на другую сторону операции; делать это, рискует неверность и / или бесконечное рекурсию, в зависимости от того, как вы это делаете)
  2. если ты нет знать, как реализовать операцию самостоятельно, всегда вернуть NotImplemented, поэтому Python может делегировать к реализации другого операнда

Проблема с not self.__eq__(other)

def __ne__(self, other):
    return not self.__eq__(other)

никогда не делегирует на другую сторону (и неверно, если __eq__ правильно возвращает NotImplemented). Когда self.__eq__(other) возвращается NotImplemented (который является «правдой»), вы молча вернусь False, так A() != something_A_knows_nothing_about возвращается False, когда он должен был проверить, если something_A_knows_nothing_about знал, как сравнить с экземплярами A, и если это не так, это должно было вернуть True (Поскольку, поскольку ни одна из сторон не знает, как сравнивать с другой, они считаются не равными друг другу). Если A.__eq__ неверно реализован (возвращается False вместо NotImplemented Когда он не распознает другую сторону), то это «правильно» от AПерспектива, возвращаясь True (поскольку A не думает, что это равно, так что это не равно), но это может быть не так от something_A_knows_nothing_aboutПерспектива, так как он даже не спросил something_A_knows_nothing_about; A() != something_A_knows_nothing_about оказывается True, но something_A_knows_nothing_about != A() мог False, или любое другое возвращаемое значение.

Проблема с not self == other

def __ne__(self, other):
    return not self == other

более тонкий. Это будет правильно на 99% классов, включая все классы, для которых __ne__ логическая инверсия __eq__. Отказ Но not self == other ломает оба правила, упомянутые выше, что означает для классов, где __ne__ нет логический обратный __eq__, результаты еще раз не рефлексивны, потому что один из операндов никогда не спрашивает, может ли она реализовать __ne__ Вообще, даже если другой операнд не может. Самый простой пример - класс Wirdo, который возвращает False для все Сравнение, так A() == Incomparable() а также A() != Incomparable() оба возврата False. Отказ С правильной реализацией A.__ne__ (тот, который возвращает NotImplemented Когда он не знает, как сделать сравнение), отношения рефлексивны; A() != Incomparable() а также Incomparable() != A() договориться о результате (потому что в прежнем случае, A.__ne__ возвращается NotImplemented, тогда Incomparable.__ne__ возвращается False, пока во второй Incomparable.__ne__ возвращается False напрямую). Но когда A.__ne__ реализован как return not self == other, A() != Incomparable() возвращается True (потому что A.__eq__ Возвращает, нет NotImplemented, тогда Incomparable.__eq__ возвращается False, а также A.__ne__ инверты, что True), пока Incomparable() != A() возвращается False.

Вы можете увидеть пример этого в действии здесь.

Очевидно, класс, который всегда возвращается False для обоих __eq__ а также __ne__ немного странно. Но как упоминалось ранее, __eq__ а также __ne__ даже не нужно возвращаться True/False; SQLALCHEMY ORM имеет классы с компараторами, которые возвращают специальный прокси-объект для застройки запроса, а не True/False Вообще (они «правда», если они оцениваются в булевом контексте, но они никогда не должны оцениваться в таком контексте).

Неспособность перегружать __ne__ Правильно, ты буду Перерыв классов такого рода, как код:

 results = session.query(MyTable).filter(MyTable.fieldname != MyClassWithBadNE())

будет работать (предполагая, что sqlalchemy знает, как вставить MyClassWithBadNE в SQL String вообще; Это можно сделать с помощью адаптеров типа без MyClassWithBadNE необходимо вообще сотрудничать), передавая ожидаемый прокси-объект к filter, пока:

 results = session.query(MyTable).filter(MyClassWithBadNE() != MyTable.fieldname)

закончится прохождение filter равнина False, потому что self == other Возвращает прокси-объект и not self == other просто преобразует правдоподобный объект прокси False. Отказ С надеждой, filter бросает исключение на обработку неверных аргументов, как False. Отказ Пока я уверен, что многие утверждают, что MyTable.fieldname должен Будьте последовательно на левой стороне сравнения, факт остается фактом, что нет программной причины для обеспечения того, чтобы обеспечить это в общем случае, и правильный универсальный __ne__ будет работать в любом случае, пока return not self == other работает только в одном договоренности.

Краткий ответ: Да (но прочитайте документацию, чтобы сделать это правильно)

Реализация Shadowranger __ne__ Метод является правильным (в том смысле, что он ведет себя точно так же, как реализация Python Python по умолчанию):

def __ne__(self, other):
    result = self.__eq__(other)

    if result is not NotImplemented:
        return not result

    return NotImplemented

Реализация зала Аарона not self == other принадлежащий __ne__ Метод неверный, как никогда не может вернуться NotImplemented (not NotImplemented является False) и поэтому __ne__ метод, который имеет приоритет, может никогда не вернуться на __ne__ Метод, который не имеет приоритета. not self == other используется для реализации Python по умолчанию Python 3 __ne__ метод, но это был ошибкой, и он был исправлен в Python 3.4 января 2015 года, как заметил Shadowranger (см. Выпуск # 21408.).

Реализация операторов сравнения

То Справочник языка 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 type(left) != type(right) and 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 type(left) != type(right) and 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 type(left) != type(right) and 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 type(left) != type(right) and 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 type(left) != type(right) and 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 type(left) != type(right) and 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__"
  1. != вызовы A.__ne__.
  2. A.__ne__ вызовы A.__eq__.
  3. A.__eq__ возвращается NotImplemented.
  4. != вызовы B.__ne__.
  5. 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
  1. != вызовы A.__ne__.
  2. A.__ne__ вызовы A.__eq__.
  3. A.__eq__ возвращается True.
  4. != возвращается 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
  1. != вызовы A.__ne__.
  2. A.__ne__ вызовы ==.
  3. == вызовы A.__eq__.
  4. A.__eq__ возвращается NotImplemented.
  5. == вызовы B.__eq__.
  6. B.__eq__ возвращается NotImplemented.
  7. == возвращается A() is B(), то есть False.
  8. 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
  1. != вызовы A.__ne__.
  2. A.__ne__ вызовы ==.
  3. == вызовы A.__eq__.
  4. A.__eq__ возвращается True.
  5. A.__ne__ возвращается not True, то есть False.

Реализация по умолчанию __ne__ Метод также вернулся False в этом случае.

Поскольку эта реализация не может повторить поведение реализации по умолчанию __ne__ метод, когда то __eq__ Метод возвращает NotImplemented, это неверно.

Если все __eq__, __ne__, __lt__, __ge__, __le__, а также __gt__ иметь смысл для класса, то просто реализуйте __cmp__ вместо. В противном случае, делайте, как вы делаете, из-за бита Даниэль Дипаооло сказал (пока я проверял его вместо того, чтобы смотреть это;))

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top