Summary. Implement the Python __eq__ method to define the equality logic for comparing two objects using the equal operator ( == )
Python internally calls x. __gt__(y) to obtain a return value when comparing two objects using x > y . The return value can be any data type because any value can automatically converted to a Boolean by using the bool() built-in function. If the __gt__() method is not defined, Python will raise a TypeError .
Magic methods in Python are the special methods that start and end with the double underscores. They are also called dunder methods. Magic methods are not meant to be invoked directly by you, but the invocation happens internally from the class on a certain action.
Python, should I implement
__ne__()
operator based on__eq__
?
==
, not __eq__
In Python 3, !=
is the negation of ==
by default, so you are not even required to write a __ne__
, and the documentation is no longer opinionated on writing one.
Generally speaking, for Python 3-only code, don't write one unless you need to overshadow the parent implementation, e.g. for a builtin object.
That is, keep in mind Raymond Hettinger's comment:
The
__ne__
method follows automatically from__eq__
only if__ne__
isn't already defined in a superclass. So, if you're inheriting from a builtin, it's best to override both.
If you need your code to work in Python 2, follow the recommendation for Python 2 and it will work in Python 3 just fine.
In Python 2, Python itself does not automatically implement any operation in terms of another - therefore, you should define the __ne__
in terms of ==
instead of the __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)`
See proof that
__ne__()
operator based on __eq__
and __ne__
in Python 2 at allprovides incorrect behavior in the demonstration below.
The documentation for Python 2 says:
There are no implied relationships among the comparison operators. The truth of
x==y
does not imply thatx!=y
is false. Accordingly, when defining__eq__()
, one should also define__ne__()
so that the operators will behave as expected.
So that means that if we define __ne__
in terms of the inverse of __eq__
, we can get consistent behavior.
This section of the documentation has been updated for Python 3:
By default,
__ne__()
delegates to__eq__()
and inverts the result unless it isNotImplemented
.
and in the "what's new" section, we see this behavior has changed:
!=
now returns the opposite of==
, unless==
returnsNotImplemented
.
For implementing __ne__
, we prefer to use the ==
operator instead of using the __eq__
method directly so that if self.__eq__(other)
of a subclass returns NotImplemented
for the type checked, Python will appropriately check other.__eq__(self)
From the documentation:
The
NotImplemented
objectThis type has a single value. There is a single object with this value. This object is accessed through the built-in name
NotImplemented
. Numeric methods and rich comparison methods may return this value if they do not implement the operation for the operands provided. (The interpreter will then try the reflected operation, or some other fallback, depending on the operator.) Its truth value is true.
When given a rich comparison operator, if they're not the same type, Python checks if the other
is a subtype, and if it has that operator defined, it uses the other
's method first (inverse for <
, <=
, >=
and >
). If NotImplemented
is returned, then it uses the opposite's method. (It does not check for the same method twice.) Using the ==
operator allows for this logic to take place.
Semantically, you should implement __ne__
in terms of the check for equality because users of your class will expect the following functions to be equivalent for all instances of 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
That is, both of the above functions should always return the same result. But this is dependent on the programmer.
__ne__
based on __eq__
:First the setup:
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."""
Instantiate non-equivalent instances:
right1, right2 = ComparableRight(1), ChildComparableRight(2)
wrong1, wrong2 = ComparableWrong(1), ChildComparableWrong(2)
right_py3_1, right_py3_2 = BaseEquatable(1), ChildComparablePy3(2)
(Note: while every second assertion of each of the below is equivalent and therefore logically redundant to the one before it, I'm including them to demonstrate that order does not matter when one is a subclass of the other.)
These instances have __ne__
implemented with ==
:
assert not right1 == right2
assert not right2 == right1
assert right1 != right2
assert right2 != right1
These instances, testing under Python 3, also work correctly:
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
And recall that these have __ne__
implemented with __eq__
- while this is the expected behavior, the implementation is incorrect:
assert not wrong1 == wrong2 # These are contradicted by the
assert not wrong2 == wrong1 # below unexpected behavior!
Note that this comparison contradicts the comparisons above (not wrong1 == wrong2
).
>>> assert wrong1 != wrong2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError
and,
>>> assert wrong2 != wrong1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError
__ne__
in Python 2For evidence that you should not skip implementing __ne__
in Python 2, see these equivalent objects:
>>> right_py3_1, right_py3_1child = BaseEquatable(1), ChildComparablePy3(1)
>>> right_py3_1 != right_py3_1child # as evaluated in Python 2!
True
The above result should be False
!
The default CPython implementation for __ne__
is in typeobject.c
in 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__
uses __eq__
?Python 3's default __ne__
implementation detail at the C level uses __eq__
because the higher level ==
(PyObject_RichCompare) would be less efficient - and therefore it must also handle NotImplemented
.
If __eq__
is correctly implemented, then the negation of ==
is also correct - and it allows us to avoid low level implementation details in our __ne__
.
Using ==
allows us to keep our low level logic in one place, and avoid addressing NotImplemented
in __ne__
.
One might incorrectly assume that ==
may return NotImplemented
.
It actually uses the same logic as the default implementation of __eq__
, which checks for identity (see do_richcompare and our evidence below)
class Foo:
def __ne__(self, other):
return NotImplemented
__eq__ = __ne__
f = Foo()
f2 = Foo()
And the comparisons:
>>> f == f
True
>>> f != f
False
>>> f2 == f
False
>>> f2 != f
True
Don't take my word for it, let's see what's more performant:
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
I think these performance numbers speak for themselves:
>>> 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
This makes sense when you consider that low_level_python
is doing logic in Python that would otherwise be handled on the C level.
Another answerer writes:
Aaron Hall’s implementation
not self == other
of the__ne__
method is incorrect as it can never returnNotImplemented
(not NotImplemented
isFalse
) and therefore the__ne__
method that has priority can never fall back on the__ne__
method that does not have priority.
Having __ne__
never return NotImplemented
does not make it incorrect. Instead, we handle prioritization with NotImplemented
via the check for equality with ==
. Assuming ==
is correctly implemented, we're done.
not self == other
used to be the default Python 3 implementation of the__ne__
method but it was a bug and it was corrected in Python 3.4 on January 2015, as ShadowRanger noticed (see issue #21408).
Well, let's explain this.
As noted earlier, Python 3 by default handles __ne__
by first checking if self.__eq__(other)
returns NotImplemented
(a singleton) - which should be checked for with is
and returned if so, else it should return the inverse. Here is that logic written as a class 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
This is necessary for correctness for C level Python API, and it was introduced in Python 3, making
__ne__
methods in this patch to close Issue 21408 and__ne__
methods in the follow-on cleanup removed hereredundant. All relevant __ne__
methods were removed, including ones implementing their own check as well as ones that delegate to __eq__
directly or via ==
- and ==
was the most common way of doing so.
Our persistent critic provides a pathological example to make the case for handling NotImplemented
in __ne__
, valuing symmetry above all else. Let's steel-man the argument with a clear example:
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)
So, by this logic, in order to maintain symmetry, we need to write the complicated __ne__
, regardless of Python version.
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)
Apparently we should give no mind that these instances are both equal and not equal.
I propose that symmetry is less important than the presumption of sensible code and following the advice of the documentation.
However, if A had a sensible implementation of __eq__
, then we could still follow my direction here and we would still have symmetry:
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)
For Python 2 compatible code, use ==
to implement __ne__
. It is more:
In Python 3 only, use the low-level negation on the C level - it is even more simple and performant (though the programmer is responsible for determining that it is correct).
Again, do not write low-level logic in high level Python.
Yes, that's perfectly fine. In fact, the documentation urges you to define __ne__
when you define __eq__
:
There are no implied relationships among the comparison operators. The truth of
x==y
does not imply thatx!=y
is false. Accordingly, when defining__eq__()
, one should also define__ne__()
so that the operators will behave as expected.
In a lot of cases (such as this one), it will be as simple as negating the result of __eq__
, but not always.
Just for the record, a canonically correct and cross Py2/Py3 portable __ne__
would look like:
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
This works with any __eq__
you might define:
not (self == other)
, doesn't interfere with in some annoying/complex cases involving comparisons where one of the classes involved doesn't imply that the result of __ne__
is the same as the result of not
on __eq__
(e.g. SQLAlchemy's ORM, where both __eq__
and __ne__
return special proxy objects, not True
or False
, and trying to not
the result of __eq__
would return False
, rather than the correct proxy object). not self.__eq__(other)
, this correctly delegates to the __ne__
of the other instance when self.__eq__
returns NotImplemented
(not self.__eq__(other)
would be extra wrong, because NotImplemented
is truthy, so when __eq__
didn't know how to perform the comparison, __ne__
would return False
, implying that the two objects were equal when in fact the only object asked had no idea, which would imply a default of not equal)If your __eq__
doesn't use NotImplemented
returns, this works (with meaningless overhead), if it does use NotImplemented
sometimes, this handles it properly. And the Python version check means that if the class is import
-ed in Python 3, __ne__
is left undefined, allowing Python's native, efficient fallback __ne__
implementation (a C version of the above) to take over.
The explanation of why you do this instead of other solutions is somewhat arcane. Python has a couple general rules about overloading operators, and comparison operators in particular:
LHS OP RHS
, try LHS.__op__(RHS)
, and if that returns NotImplemented
, try RHS.__rop__(LHS)
. Exception: If RHS
is a subclass of LHS
's class, then test RHS.__rop__(LHS)
first. In the case of comparison operators, __eq__
and __ne__
are their own "rop"s (so the test order for __ne__
is LHS.__ne__(RHS)
, then RHS.__ne__(LHS)
, reversed if RHS
is a subclass of LHS
's class)LHS.__eq__(RHS)
returning True
does not imply LHS.__ne__(RHS)
returns False
(in fact, the operators aren't even required to return boolean values; ORMs like SQLAlchemy intentionally do not, allowing for a more expressive query syntax). As of Python 3, the default __ne__
implementation behaves this way, but it's not contractual; you can override __ne__
in ways that aren't strict opposites of __eq__
.So when you overload an operator, you have two jobs:
NotImplemented
, so Python can delegate to the other operand's implementationnot self.__eq__(other)
def __ne__(self, other):
return not self.__eq__(other)
never delegates to the other side (and is incorrect if __eq__
properly returns NotImplemented
). When self.__eq__(other)
returns NotImplemented
(which is "truthy"), you silently return False
, so A() != something_A_knows_nothing_about
returns False
, when it should have checked if something_A_knows_nothing_about
knew how to compare to instances of A
, and if it doesn't, it should have returned True
(since if neither side knows how to compare to the other, they're considered not equal to one another). If A.__eq__
is incorrectly implemented (returning False
instead of NotImplemented
when it doesn't recognize the other side), then this is "correct" from A
's perspective, returning True
(since A
doesn't think it's equal, so it's not equal), but it might be wrong from something_A_knows_nothing_about
's perspective, since it never even asked something_A_knows_nothing_about
; A() != something_A_knows_nothing_about
ends up True
, but something_A_knows_nothing_about != A()
could False
, or any other return value.
not self == other
def __ne__(self, other):
return not self == other
is more subtle. It's going to be correct for 99% of classes, including all classes for which __ne__
is the logical inverse of __eq__
. But not self == other
breaks both of the rules mentioned above, which means for classes where __ne__
isn't the logical inverse of __eq__
, the results are once again non-symmetric, because one of the operands is never asked if it can implement __ne__
at all, even if the other operand can't. The simplest example is a weirdo class which returns False
for all comparisons, so A() == Incomparable()
and A() != Incomparable()
both return False
. With a correct implementation of A.__ne__
(one which returns NotImplemented
when it doesn't know how to do the comparison), the relationship is symmetric; A() != Incomparable()
and Incomparable() != A()
agree on the outcome (because in the former case, A.__ne__
returns NotImplemented
, then Incomparable.__ne__
returns False
, while in the latter, Incomparable.__ne__
returns False
directly). But when A.__ne__
is implemented as return not self == other
, A() != Incomparable()
returns True
(because A.__eq__
returns, not NotImplemented
, then Incomparable.__eq__
returns False
, and A.__ne__
inverts that to True
), while Incomparable() != A()
returns False.
You can see an example of this in action here.
Obviously, a class that always returns False
for both __eq__
and __ne__
is a little strange. But as mentioned before, __eq__
and __ne__
don't even need to return True
/False
; the SQLAlchemy ORM has classes with comparators that returns a special proxy object for query building, not True
/False
at all (they're "truthy" if evaluated in a boolean context, but they're never supposed to be evaluated in such a context).
By failing to overload __ne__
properly, you will break classes of that sort, as the code:
results = session.query(MyTable).filter(MyTable.fieldname != MyClassWithBadNE())
will work (assuming SQLAlchemy knows how to insert MyClassWithBadNE
into a SQL string at all; this can be done with type adapters without MyClassWithBadNE
having to cooperate at all), passing the expected proxy object to filter
, while:
results = session.query(MyTable).filter(MyClassWithBadNE() != MyTable.fieldname)
will end up passing filter
a plain False
, because self == other
returns a proxy object, and not self == other
just converts the truthy proxy object to False
. Hopefully, filter
throws an exception on being handled invalid arguments like False
. While I'm sure many will argue that MyTable.fieldname
should be consistently on the left hand side of the comparison, the fact remains that there is no programmatic reason to enforce this in the general case, and a correct generic __ne__
will work either way, while return not self == other
only works in one arrangement.
__ne__
@ShadowRanger’s implementation of the special method __ne__
is the correct one:
def __ne__(self, other):
result = self.__eq__(other)
if result is not NotImplemented:
return not result
return NotImplemented
It also happens to be the default implementation of the special method __ne__
since Python 3.4, as stated in the Python documentation:
By default,
__ne__()
delegates to__eq__()
and inverts the result unless it isNotImplemented
.
Also note that returning the value NotImplemented
for unsupported operands is not specific to the special method __ne__
. In fact, all the special comparison methods1 and special numeric methods2 should return the value NotImplemented
for unsupported operands, as specified in the Python documentation:
NotImplemented
This type has a single value. There is a single object with this value. This object is accessed through the built-in name
NotImplemented
. Numeric methods and rich comparison methods should return this value if they do not implement the operation for the operands provided. (The interpreter will then try the reflected operation, or some other fallback, depending on the operator.) Its truth value is true.
An example for the special numeric methods is given in the Python documentation:
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 The special comparison methods: __lt__
, __le__
, __eq__
, __ne__
, __gt__
and __ge__
.
2 The special numeric methods: __add__
, __sub__
, __mul__
, __matmul__
, __truediv__
, __floordiv__
, __mod__
, __divmod__
, __pow__
, __lshift__
, __rshift__
, __and__
, __xor__
, __or__
and their __r*__
reflected and __i*__
in-place counterparts.
__ne__
#1@Falmarri’s implementation of the special method __ne__
is incorrect:
def __ne__(self, other):
return not self.__eq__(other)
The problem with this implementation is that it does not fall back on the special method __ne__
of the other operand as it never returns the value NotImplemented
(the expression not self.__eq__(other)
evaluates to the value True
or False
, including when its subexpression self.__eq__(other)
evaluates to the value NotImplemented
since the expression bool(NotImplemented)
evaluates to the value True
). The Boolean evaluation of the value NotImplemented
breaks the complement relationship between the comparison operators !=
and ==
:
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’s implementation of the special method __ne__
is also incorrect:
def __ne__(self, other):
return not self == other
The problem with this implementation is that it directly falls back on the special method __eq__
of the other operand, bypassing the special method __ne__
of the other operand as it never returns the value NotImplemented
(the expression not self == other
falls back on the special method __eq__
of the other operand and evaluates to the value True
or False
). Bypassing a method is incorrect because that method may have side effects like updating the state of the object:
class Correct:
def __init__(self):
self.state = False
def __ne__(self, other):
self.state = True
result = self.__eq__(other)
if result is not NotImplemented:
return not result
return NotImplemented
class Incorrect:
def __init__(self):
self.state = False
def __ne__(self, other):
self.state = True
return not self == other
x, y = Correct(), Correct()
assert x != y
assert x.state == y.state
x, y = Incorrect(), Incorrect()
assert x != y
assert x.state == y.state # AssertionError
In mathematics, a binary relation R over a set X is a set of ordered pairs (x, y) in X2. The statement (x, y) in R reads ‘x is R-related to y’ and is denoted by xRy.
Properties of a binary relation R over a set X:
Operations on two binary relations R and S over a set X:
Relationships between comparison relations that are always valid:
Relationships between comparison relations that are only valid for connex order relations:
So to correctly implement in Python the comparison operators ==
, !=
, <
, >
, <=
, and >=
corresponding to the comparison relations =, ≠, <, >, ≤, and ≥, all the above mathematical properties and relationships should hold.
A comparison operation x operator y
calls the special comparison method __operator__
of the class of one of its operands:
class X:
def __operator__(self, other):
# implementation
Since R is reflexive implies xRx, a reflexive comparison operation x operator y
(x == y
, x <= y
and x >= y
) or reflexive special comparison method call x.__operator__(y)
(x.__eq__(y)
, x.__le__(y)
and x.__ge__(y)
) should evaluate to the value True
if x
and y
are identical, that is if the expression x is y
evaluates to True
. Since R is irreflexive implies not xRx, an irreflexive comparison operation x operator y
(x != y
, x < y
and x > y
) or irreflexive special comparison method call x.__operator__(y)
(x.__ne__(y)
, x.__lt__(y)
and x.__gt__(y)
) should evaluate to the value False
if x
and y
are identical, that is if the expression x is y
evaluates to True
. The reflexive property is considered by Python for the comparison operator ==
and associated special comparison method __eq__
but surprisingly not considered for the comparison operators <=
and >=
and associated special comparison methods __le__
and __ge__
, and the irreflexive property is considered by Python for the comparison operator !=
and associated special comparison method __ne__
but surprisingly not considered for the comparison operators <
and >
and associated special comparison methods __lt__
and __gt__
. The ignored comparison operators instead raise the exception TypeError
(and associated special comparison methods instead return the value NotImplemented
), as explained in the Python documentation:
The default behavior for equality comparison (
==
and!=
) is based on the identity of the objects. Hence, equality comparison of instances with the same identity results in equality, and equality comparison of instances with different identities results in inequality. A motivation for this default behavior is the desire that all objects should be reflexive (i.e.x is y
impliesx == y
).A default order comparison (
<
,>
,<=
, and>=
) is not provided; an attempt raisesTypeError
. A motivation for this default behavior is the lack of a similar invariant as for equality. [This is incorrect since<=
and>=
are reflexive like==
, and<
and>
are irreflexive like!=
.]
The class object
provides the default implementations of the special comparison methods which are inherited by all its subclasses, as explained in the Python documentation:
object.__lt__(self, other)
object.__le__(self, other)
object.__eq__(self, other)
object.__ne__(self, other)
object.__gt__(self, other)
object.__ge__(self, other)
These are the so-called “rich comparison” methods. The correspondence between operator symbols and method names is as follows:
x<y
callsx.__lt__(y)
,x<=y
callsx.__le__(y)
,x==y
callsx.__eq__(y)
,x!=y
callsx.__ne__(y)
,x>y
callsx.__gt__(y)
, andx>=y
callsx.__ge__(y)
.A rich comparison method may return the singleton
NotImplemented
if it does not implement the operation for a given pair of arguments.[…]
There are no swapped-argument versions of these methods (to be used when the left argument does not support the operation but the right argument does); rather,
__lt__()
and__gt__()
are each other’s reflection,__le__()
and__ge__()
are each other’s reflection, and__eq__()
and__ne__()
are their own reflection. If the operands are of different types, and right operand’s type is a direct or indirect subclass of the left operand’s type, the reflected method of the right operand has priority, otherwise the left operand’s method has priority. Virtual subclassing is not considered.
Since R = (RT)T, a comparison xRy is equivalent to the converse comparison yRTx (informally named ‘reflected’ in the Python documentation). So there are two ways to compute the result of a comparison operation x operator y
: calling either x.__operator__(y)
or y.__operatorT__(x)
. Python uses the following computing strategy:
x.__operator__(y)
unless the right operand’s class is a descendant of the left operand’s class, in which case it calls y.__operatorT__(x)
(allowing classes to override their ancestors’ converse special comparison method).x
and y
are unsupported (indicated by the return value NotImplemented
), it calls the converse special comparison method as a 1st fallback.x
and y
are unsupported (indicated by the return value NotImplemented
), it raises the exception TypeError
except for the comparison operators ==
and !=
for which it compares respectively the identity and non-identity of the operands x
and y
as a 2nd fallback (leveraging the reflexivity property of ==
and irreflexivity property of !=
).In CPython this is implemented in C code, which can be translated into Python code (with the names eq
for ==
, ne
for !=
, lt
for <
, gt
for >
, le
for <=
and ge
for >=
):
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
Since R = ¬(¬R), a comparison xRy is equivalent to the complement comparison ¬(x¬Ry). ≠ is the complement of =, so the special method __ne__
is implemented in terms of the special method __eq__
for supported operands by default, while the other special comparison methods are implemented independently by default (the fact that ≤ is the union of < and =, and ≥ is the union of > and = is surprisingly not considered, which means that currently the special methods __le__
and __ge__
should be user implemented), as explained in the Python documentation:
By default,
__ne__()
delegates to__eq__()
and inverts the result unless it isNotImplemented
. There are no other implied relationships among the comparison operators, for example, the truth of(x<y or x==y)
does not implyx<=y
.
In CPython this is implemented in C code, which can be translated into Python code:
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
So by default:
x operator y
raises the exception TypeError
except for the comparison operators ==
and !=
for which it returns respectively the values True
and False
if the operands x
and y
are respectively identical and non-identical, and the values False
and True
otherwise;x.__operator__(y)
returns the value NotImplemented
except for the special comparison methods __eq__
and __ne__
for which it returns respectively the values True
and False
if the operands x
and y
are respectively identical and non-identical, and the value NotImplemented
otherwise.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With