У меня есть класс, в котором я хочу переопределить __eq__
метод. Кажется, имеет смысл переопределить и __ne__
метод, но имеет ли смысл реализовать __ne__
его __eq__
как таковой?
class A:
def __init__(self, attr):
self.attr = attr
def __eq__(self, other):
return self.attr == other.attr
def __ne__(self, other):
return not self.__eq__(other)
Или есть что-то, чего мне не хватает в том, как Python использует эти методы, что делает это не очень хорошей идеей?
python
comparison
operators
python-datamodel
Фалмарри
источник
источник
__ne__
using__eq__
, а только ее реализации.NotImplemented
возврат с одной стороны как сигнал для делегирования__ne__
другой стороне,not self == other
(при условии, что операнд__eq__
не знает, как сравнить другой операнд) неявно делегирует__eq__
другой стороне, а затем инвертирует его. Для странных типов, например полей ORM SQLAlchemy, это вызывает проблемы .__ne__
автоматически делегируется,__eq__
и цитата в этом ответе больше не существует в документах. Суть в том, что это совершенно pythonic только для реализации__eq__
и__ne__
делегирования.Краткий ответ: не внедряйте, но если нужно, используйте
==
, а не__eq__
В Python 3 по умолчанию используется
!=
отрицание==
, поэтому от вас даже не требуется писать a__ne__
, и документация больше не требует его написания.Вообще говоря, для кода, предназначенного только для Python 3, не пишите его, если вам не нужно перекрывать родительскую реализацию, например, для встроенного объекта.
То есть имейте в виду комментарий Раймона Хеттингера :
Если вам нужно, чтобы ваш код работал на Python 2, следуйте рекомендациям для Python 2, и он будет отлично работать на Python 3.
В Python 2 сам Python не реализует автоматически какую-либо операцию в терминах другой, поэтому вам следует определять
__ne__
in в терминах==
вместо__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 говорится:
Это означает, что если мы определим
__ne__
через обратное__eq__
, мы можем добиться согласованного поведения.Этот раздел документации был обновлен для Python 3:
а в разделе «Что нового» мы видим, что поведение изменилось:
Для реализации
__ne__
мы предпочитаем использовать==
оператор вместо использования__eq__
метода напрямую, чтобы, еслиself.__eq__(other)
подкласс возвращаетNotImplemented
проверенный тип, Python соответствующим образом проверитother.__eq__(self)
Из документации :Когда дается богатый оператор сравнения, если они не тот же самый тип, Python проверяет , является ли
other
это подтип, и если у него есть , что оператор , определенный, он используетother
первый метод «s (обратный для<
,<=
,>=
и>
). Если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
Реализация 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 (Py_TYPE(self)->tp_richcompare == NULL) { res = Py_NotImplemented; Py_INCREF(res); break; } res = (*Py_TYPE(self)->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); } } break;
Но по умолчанию
__ne__
использует__eq__
?Детали
__ne__
реализации Python 3 по умолчанию на уровне 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.Ответ некоторым критикам
Другой отвечающий пишет:
То, что вы
__ne__
никогда не вернетесьNotImplemented
, не делает его неправильным. Вместо этого мы обрабатываем приоритизацию сNotImplemented
помощью проверки на равенство с==
. Предполагая==
, что все выполнено правильно, мы закончили.Что ж, давайте это объясним.
Как отмечалось ранее, Python 3 по умолчанию обрабатывает
__ne__
, сначала проверяя,self.__eq__(other)
возвращает ли онNotImplemented
(синглтон), что следует проверить с помощью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 API уровня C, и это было введено в Python 3, что делает
__ne__
методы в этом патче , чтобы закрыть Issue 21408 и__ne__
методы в последующей деятельности по очистке удаляются здесьизбыточный. Все соответствующие
__ne__
методы были удалены, в том числе те, которые реализуют собственную проверку, а также те, которые делегируют полномочия__eq__
напрямую или через==
- и это==
был наиболее распространенный способ сделать это.Важна ли симметрия?
Наш настойчивый критик оказывает патологический пример , чтобы сделать дело для обработки
NotImplemented
в__ne__
, оценке симметрии выше всего остального. Давайте проиллюстрируем аргумент ясным примером:class B: """ this class has no __eq__ implementation, but asserts any instance is not equal to any other object """ def __ne__(self, other): return True class A: "This class asserts instances are equivalent to all other objects" def __eq__(self, other): return True >>> A() == B(), B() == A(), A() != B(), B() != A() (True, True, False, True)
Итак, по этой логике, чтобы сохранить симметрию, нам нужно написать сложное
__ne__
, независимо от версии Python.class B: def __ne__(self, other): return True class A: def __eq__(self, other): return True def __ne__(self, other): result = other.__eq__(self) if result is NotImplemented: return NotImplemented return not result >>> A() == B(), B() == A(), A() != B(), B() != A() (True, True, True, True)
Очевидно, нам не следует думать, что эти случаи равны и не равны.
Я полагаю, что симметрия менее важна, чем презумпция разумного кода и следование советам документации.
Однако, если бы у A была разумная реализация
__eq__
, мы все равно могли бы следовать моему направлению здесь, и у нас все еще была бы симметрия:class B: def __ne__(self, other): return True class A: def __eq__(self, other): return False # <- this boolean changed... >>> A() == B(), B() == A(), A() != B(), B() != A() (False, False, True, True)
Вывод
Для кода, совместимого с Python 2, используйте
==
для реализации__ne__
. Это больше:Только в Python 3 используйте низкоуровневое отрицание на уровне C - оно еще более простое и производительное (хотя программист несет ответственность за определение его правильности ).
Опять же, не пишите логику низкого уровня на Python высокого уровня.
источник
a1 != c2
вернулиFalse
--- он не запустилсяa1.__ne__
, ноc2.__ne__
, что отрицает метод миксина__eq__
. ПосколькуNotImplemented
это правда, тоnot NotImplemented
естьFalse
.not (self == other)
, но никто не утверждает, что это не быстро (ну, в любом случае, быстрее, чем любой другой вариант на Py2). Проблема в том, что в некоторых случаях это неправильно ; Сам Python имел обыкновение делатьnot (self == other)
, но изменился, потому что он был неправильным при наличии произвольных подклассов . Ответ на быстрый неверный ответ все равно неверен .__ne__
делегатов__eq__
(обеих сторон, если необходимо), но никогда не возвращается обратно к__ne__
другой стороне, даже когда обе__eq__
«сдаются». Правильные__ne__
делегаты для себя__eq__
, но если это вернетсяNotImplemented
, он возвращается к другой стороне__ne__
, а не инвертирует другую сторону__eq__
(поскольку другая сторона может явно не участвовать в делегировании__eq__
, и вы не должны принимать за это решение).__eq__
не__ne__
возвращает ниTrue
илиFalse
, а скорее прокси-объект (который оказывается «правдивым»). Неправильная реализация__ne__
означает, что порядок сравнения имеет значение для сравнения (вы получаете прокси только в одном порядке).__ne__
полностью опустить . Через год Py2 умрет, и мы игнорируем это. :-)Для справки, канонически правильный и перекрестный переносимый 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__
возвращают специальные прокси-объекты, notTrue
orFalse
, и попытка вернутьnot
результат of , а не правильный прокси-объект).__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 является -ed,__ne__
он остается неопределенным, что позволяет использовать собственную эффективную резервную__ne__
реализацию Python (версия C из вышеупомянутого) .Зачем это нужно
Правила перегрузки Python
Объяснение того, почему вы делаете это вместо других решений, несколько загадочно. В Python есть пара общих правил относительно операторов перегрузки и, в частности, операторов сравнения:
LHS OP RHS
попробуйтеLHS.__op__(RHS)
, а если вернетсяNotImplemented
, попробуйтеRHS.__rop__(LHS)
. Исключение: еслиRHS
это подклассLHS
класса,RHS.__rop__(LHS)
сначала проверьте . В случае операторов сравнения,__eq__
и__ne__
их собственный «ПРП» s (так что тест заказ на__ne__
этоLHS.__ne__(RHS)
, тоRHS.__ne__(LHS)
, сторнируется , еслиRHS
это подклассLHS
класса «s)LHS.__eq__(RHS)
возвратTrue
не подразумеваетLHS.__ne__(RHS)
возвратаFalse
(на самом деле, операторы даже не обязаны возвращать логические значения; ORM, такие как SQLAlchemy, намеренно этого не делают, что позволяет использовать более выразительный синтаксис запроса). В Python 3__ne__
реализация по умолчанию ведет себя так, но не по контракту; вы можете переопределить__ne__
способами, которые не являются строгими противоположностями__eq__
.Как это применимо к компараторам с перегрузкой
Итак, когда вы перегружаете оператора, у вас есть две задачи:
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
; ORM SQLAlchemy имеет классы с компараторами, которые возвращают специальный прокси-объект для построения запроса, а неTrue
/False
вообще (они «правдивы», если оцениваются в логическом контексте, но они никогда не должны оцениваться в таком контексте).Будучи не в состоянии перегрузки
__ne__
должным образом, вы будете нарушать классы такого рода, как код:будет работать (при условии, что SQLAlchemy вообще знает, как вставлять
MyClassWithBadNE
в строку SQL; это можно сделать с помощью адаптеров типов безMyClassWithBadNE
необходимости вообще сотрудничать), передавая ожидаемый прокси-объектfilter
, в то время как:в конечном итоге передаст
filter
простойFalse
, потому чтоself == other
возвращает прокси-объект иnot self == other
просто преобразует истинный прокси-объект вFalse
. Надеюсь,filter
выдает исключение при обработке недопустимых аргументов, таких какFalse
. Хотя я уверен, что многие будут утверждать, что этоMyTable.fieldname
должно быть последовательно в левой части сравнения, факт остается фактом: нет никаких программных причин для принудительного применения этого в общем случае, и правильный универсальный шаблон__ne__
будет работать в любом случае, хотяreturn not self == other
работает только в одной аранжировке.источник
Incomparable
класс , так как данный класс перерывов в дополнение отношения между!=
и==
операторов , и поэтому может рассматриваться как недействительное или «патологической» пример , как @AaronHall выразился. И я признаю, что @AaronHall был прав, когда указал, что ваш аргумент SQLAlchemy может считаться неуместным, поскольку он находится в не-логическом контексте. (Ваши аргументы все еще очень интересны и хорошо продуманы.)Правильная
__ne__
реализацияРеализация специального метода @ ShadowRanger
__ne__
является правильной:def __ne__(self, other): result = self.__eq__(other) if result is not NotImplemented: return not result return NotImplemented
Это также является реализацией специального метода по умолчанию,
__ne__
начиная с Python 3.4 , как указано в документации Python :Также обратите внимание, что возврат значения
NotImplemented
для неподдерживаемых операндов не зависит от специального метода__ne__
. Фактически, все специальные методы сравнения 1 и специальные числовые методы 2 должны возвращать значениеNotImplemented
для неподдерживаемых операндов , как указано в документации Python :Пример специальных числовых методов приведен в документации Python :
class MyIntegral(Integral): def __add__(self, other): if isinstance(other, MyIntegral): return do_my_adding_stuff(self, other) elif isinstance(other, OtherTypeIKnowAbout): return do_my_other_adding_stuff(self, other) else: return NotImplemented def __radd__(self, other): if isinstance(other, MyIntegral): return do_my_adding_stuff(other, self) elif isinstance(other, OtherTypeIKnowAbout): return do_my_other_adding_stuff(other, self) elif isinstance(other, Integral): return int(other) + int(self) elif isinstance(other, Real): return float(other) + float(self) elif isinstance(other, Complex): return complex(other) + complex(self) else: return NotImplemented
1 Специальные методы сравнения:
__lt__
,__le__
,__eq__
,__ne__
,__gt__
и__ge__
.2 Специальные числовые методы:
__add__
,__sub__
,__mul__
,__matmul__
,__truediv__
,__floordiv__
,__mod__
,__divmod__
,__pow__
,__lshift__
,__rshift__
,__and__
,__xor__
,__or__
и их__r*__
отраженные и__i*__
на месте коллеги.Неправильная
__ne__
реализация # 1@Falmarri реализация специального метода
__ne__
неверна:def __ne__(self, other): return not self.__eq__(other)
Проблема с этой реализацией заключается в том, что она не использует специальный метод
__ne__
другого операнда, поскольку он никогда не возвращает значениеNotImplemented
(выражениеnot self.__eq__(other)
оценивается как значениеTrue
илиFalse
, в том числе, когда его подвыражениеself.__eq__(other)
оценивается как значение,NotImplemented
поскольку выражениеbool(NotImplemented)
оценивается как значениеTrue
). Логическая оценка значенияNotImplemented
разрушает отношения дополнения между операторами сравнения!=
и==
:class Correct: def __ne__(self, other): result = self.__eq__(other) if result is not NotImplemented: return not result return NotImplemented class Incorrect: def __ne__(self, other): return not self.__eq__(other) x, y = Correct(), Correct() assert (x != y) is not (x == y) x, y = Incorrect(), Incorrect() assert (x != y) is not (x == y) # AssertionError
Неправильная
__ne__
реализация # 2Реализация специального метода @AaronHall
__ne__
также неверна:def __ne__(self, other): return not self == other
Проблема с этой реализацией заключается в том, что она напрямую обращается к специальному методу
__eq__
другого операнда, минуя специальный метод__ne__
другого операнда, поскольку он никогда не возвращает значениеNotImplemented
(выражениеnot self == other
возвращается к специальному методу__eq__
другого операнда и оценивается как значениеTrue
илиFalse
). Обход метода неверен, потому что этот метод может иметь побочные эффекты, такие как обновление состояния объекта:class Correct: def __init__(self): self.counter = 0 def __ne__(self, other): self.counter += 1 result = self.__eq__(other) if result is not NotImplemented: return not result return NotImplemented class Incorrect: def __init__(self): self.counter = 0 def __ne__(self, other): self.counter += 1 return not self == other x, y = Correct(), Correct() assert x != y assert x.counter == y.counter x, y = Incorrect(), Incorrect() assert x != y assert x.counter == y.counter # AssertionError
Понимание операций сравнения
В математике бинарное отношение R над множеством X - это набор упорядоченных пар ( x , y ) в X 2 . Выражение ( x , y ) в R читается как « x является R- связано с y » и обозначается xRy .
Свойства бинарного отношения R над множеством X :
Например, =. Однако только симметрично.
Например, ≤ и ≥.
Например, <и>. Однако ≠ только иррефлексивно.
Операции над двумя бинарными отношениями R и S над множеством X :
Отношения между отношениями сравнения, которые всегда действительны:
Связи между отношениями сравнения, которые действительны только для заказов Connex :
Таким образом , чтобы правильно реализовать в Python операторы сравнения
==
,!=
,<
,>
,<=
, и>=
соответствующие сравнения отношений =, ≠, <,>, ≤, и ≥, все выше математические свойства и отношения должны держать.Операция сравнения
x operator y
вызывает специальный метод сравнения__operator__
класса одного из его операндов:class X: def __operator__(self, other): # implementation
Так как R является рефлексивным означают XRX , рефлексивная операция сравнения
x operator y
(x == y
,x <= y
иx >= y
) или рефлексивный специальный вызов методы сравненияx.__operator__(y)
(x.__eq__(y)
,x.__le__(y)
иx.__ge__(y)
) следует оценить до значения ,True
еслиx
иy
являются идентичными, то есть , если выражениеx is y
принимает значениеTrue
. Поскольку R является иррефлексивным, подразумевает не xRx , операция нерефлексивного сравненияx operator y
(x != y
,x < y
иx > y
) или нерефлексивный вызов специального метода сравненияx.__operator__(y)
(x.__ne__(y)
,x.__lt__(y)
иx.__gt__(y)
) должны оцениваться как значениеFalse
еслиx
иy
идентичны, то есть если выражениеx is y
оценивается какTrue
. Рефлексивный свойство рассматривается Python для оператора сравнения==
и связанные с ними специальный метод сравнения ,__eq__
но на удивление не рассматривается для операторов сравнения<=
и>=
и связанные с ними специальные методы сравнения__le__
и__ge__
, а иррефлексивное свойство рассматривается Python для оператора сравнения!=
и связанные с ними специальный метод сравнения ,__ne__
но на удивление не рассматривается для операторов сравнения<
и>
и связанные с ними специальные методы сравнения__lt__
и__gt__
. Вместо этого игнорируемые операторы сравнения вызывают исключениеTypeError
(а соответствующие специальные методы сравнения вместо этого возвращают значениеNotImplemented
), как описано в документации Python :Класс
object
предоставляет стандартные реализации специальных методов сравнения, которые наследуются всеми его подклассами, как описано в документации Python :Поскольку R = ( R T ) T , сравнение xRy эквивалентно обратному сравнению yR T x (неофициально названное «отраженным» в документации Python). Итак, есть два способа вычислить результат операции сравнения
x operator y
: вызов либоx.__operator__(y)
илиy.__operatorT__(x)
. Python использует следующую вычислительную стратегию:x.__operator__(y)
если класс правого операнда не является потомком класса левого операнда, и в этом случае он вызываетy.__operatorT__(x)
( позволяя классам переопределять обратный специальный метод сравнения своих предков ).x
иy
не поддерживаются (на это указывает возвращаемое значениеNotImplemented
), он вызывает обратный специальный метод сравнения как 1-й резервный вариант .x
иy
не поддерживаются (указывается возвращаемым значениемNotImplemented
), он вызывает исключениеTypeError
для операторов сравнения , за исключением ,==
и!=
для которых он проверяет , соответственно , личность и нетождественность операндовx
иy
как 2 - й запасной вариант ( с использованием рефлексивности свойства==
и свойство иррефлексивности!=
).В CPython это реализовано в C код , который можно перевести в код Python (с именами
eq
для==
,ne
для!=
,lt
для<
,gt
для>
,le
для<=
иge
для>=
):def 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 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 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__}' " f"and '{type(right).__name__}'" ) return result
def 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__}' " f"and '{type(right).__name__}'" ) return result
def 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__}' " f"and '{type(right).__name__}'" ) return result
def 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__}' " f"and '{type(right).__name__}'" ) return result
Поскольку R = ¬ (¬ R ), сравнение xRy эквивалентно сравнению с дополнением ¬ ( x ¬ Ry ). ≠ является дополнением к =, поэтому специальный метод
__ne__
реализуется в терминах специального метода__eq__
для поддерживаемых операндов по умолчанию, в то время как другие специальные методы сравнения реализуются независимо по умолчанию (факт, что ≤ является объединением <и =, и ≥ представляет собой объединение> и =, что на удивление не рассматривается , что означает, что в настоящее время специальные методы__le__
и__ge__
должны быть реализованы пользователем), как описано в документации Python :В CPython это реализовано в коде C , который можно перевести в код Python:
def __eq__(self, other): return self is other or 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
Итак, по умолчанию:
x operator y
вызывает исключение,TypeError
за исключением операторов сравнения==
и!=
для которых она возвращает идентичность и неидентичность операндовx
иy
;x.__operator__(y)
возвращает значение,NotImplemented
за исключением специальных методов сравнения__eq__
и__ne__
для которых он возвращает соответственно,True
иFalse
если операндыx
иy
соответственно идентичны и неидентичны, и значение вNotImplemented
противном случае.источник
__ne__
метода по умолчанию, когда__eq__
метод возвращает NotImplemented, это неверно». -A
определяет безусловное равенство. Таким образом,A() == B()
. Таким образом ,A() != B()
должно быть ложным , и есть . Приведенные примеры являются патологическими (т.е.__ne__
не должны возвращать строку и__eq__
не должны зависеть от__ne__
- скорее,__ne__
должны зависеть от__eq__
, что является ожиданием по умолчанию в Python 3). Я по-прежнему -1 к этому ответу, пока вы не передумаете.NotImplemented
если он не реализует операцию для данной пары аргументов. По соглашениюFalse
иTrue
возвращаются для успешного сравнения. Однако эти методы могут возвращать любое значение. , поэтому, если оператор сравнения используется в логическом контексте (например, в условии оператора if), Python вызоветbool()
значение, чтобы определить, является ли результат истинным или ложным ».B
которые возвращают правдивую строку при всех проверках для__ne__
иA
возвращаютTrue
при всех проверках__eq__
. Это патологическое противоречие. При таком противоречии лучше всего было бы сделать исключение. Не знаяB
,A
не обязан соблюдатьB
реализацию__ne__
в целях симметрии. На этом этапе примера мне неважно , как работаетA
орудие__ne__
. Пожалуйста, найдите практический, непатологический случай, чтобы выразить свою точку зрения. Я обновил свой ответ, чтобы обратиться к вам.Если все
__eq__
,__ne__
,__lt__
,__ge__
,__le__
, и имеет__gt__
смысл для класса, то просто реализовать__cmp__
вместо этого. В противном случае делайте то, что делаете, из-за того, что сказал Даниэль ДиПаоло (пока я тестировал его, а не искал;))источник
__cmp__()
специальный метод больше не поддерживается в Python 3.x, поэтому вам следует привыкнуть к использованию разнообразных операторов сравнения.