Python, я должен реализовать оператор __ne __ () на основе __eq__? - PullRequest
77 голосов
/ 04 декабря 2010

У меня есть класс, в котором я хочу переопределить оператор __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 ]

95 голосов
/ 06 июня 2015

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 высокого уровня.

47 голосов
/ 04 декабря 2010

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

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

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

7 голосов
/ 03 марта 2016

Просто для записи, канонически правильный и кросс-переносной 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 on __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 -ed в Python 3, __ne__ остается неопределенным, что позволяет использовать эффективную альтернативную реализацию Python __ne__ (версия C выше) взять на себя.


Зачем это нужно

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

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

  1. (Применяется ко всем операторам). При запуске LHS OP RHS попробуйте LHS.__op__(RHS), а если возвращается NotImplemented, попробуйте RHS.__rop__(LHS). Исключение: если RHS является подклассом класса LHS, то тестируйте RHS.__rop__(LHS) first . В случае операторов сравнения __eq__ и __ne__ являются их собственными "rop" (так что порядок проверки для __ne__ равен LHS.__ne__(RHS), тогда RHS.__ne__(LHS), обратный, если RHS является подклассом * Класс 1067 *)
  2. Помимо идеи "подкачки" оператора, не существует подразумеваемой связи между операторами. Даже для одного и того же класса LHS.__eq__(RHS), возвращающий True, не означает, что LHS.__ne__(RHS) возвращает False (на самом деле, операторы даже не обязаны возвращать логические значения; ORM, такие как 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__, даже если другой операнд не может.Простейшим примером является класс странности, который возвращает 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; это можно сделать с помощью адаптеров типов без необходимости 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 работает только в одном расположении.

2 голосов
/ 03 июня 2018

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

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

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 is False), и поэтому метод __ne__, имеющий приоритет, никогда не сможет вернуться к __ne__ метод, который не имеет приоритета. not self == other раньше был реализацией метода __ne__ по умолчанию в Python 3, но это была ошибка, и она была исправлена ​​в Python 3.4 в январе 2015 года, как заметил ShadowRanger (см. проблема # 21408 ).

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

Справочник по языку Python 1023 * для состояний 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__ возвращает значение difFerent от 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, это неверно.

0 голосов
/ 04 декабря 2010

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

...