В Python, я должен реализовать нэ (оператором) производится по формулам?


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

5 ответов:

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

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

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

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

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

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

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

То есть имейте в виду комментарий Раймонда Хеттингера :

Метод __ne__ автоматически следует из __eq__ только в том случае, если __ne__ еще не определен в суперклассе. Так что, если ты ... наследуя от встроенного, лучше всего переопределить оба.

Если вам нужно, чтобы ваш код работал в Python 2, следуйте рекомендациям для Python 2, и он будет работать в Python 3 просто отлично.

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

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!

Неожиданное Поведение:

Обратите внимание, что это сравнение противоречит приведенным выше сравнениям (

).

>>> 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

Производительность

Не верьте мне на слово, давайте посмотрим, что более эффективно:

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-оно даже более простое и производительное (хотя программист отвечает за определение того, что это правильно).

Делать не написать низкоуровневую логику питона в высоком уровне.

Просто для записи, канонически правильный и перекрестный 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__ имеют смысл для класса, а затем просто реализуют __cmp__ вместо этого. В противном случае, делайте то, что вы делаете, из-за бита, который сказал Даниэль Дипаоло (в то время как я тестировал его, а не искал его;))

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

Хотя и интересно, ответ Аарона холла не является правильным способом реализации метода __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, Если он не реализует операцию для данной пары аргументов.

Не существует версий этих методов со сменными аргументами (которые будут использоваться когда левый аргумент не поддерживает операцию, а правый аргумент делает); скорее, __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 является 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, это неверно.