I have a class where I want to override the __eq__ method. It seems to make sense that I should override the __ne__ method as well. Should I implem
__ne__ implementation@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__ implementation #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__ implementation #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.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
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 orders:
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 yimpliesx == 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:
xcalls x.__lt__(y),x<=ycallsx.__le__(y),x==ycallsx.__eq__(y),x!=ycallsx.__ne__(y),x>ycallsx.__gt__(y), andx>=ycallsx.__ge__(y).A rich comparison method may return the singleton
NotImplementedif 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 tests 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(xdoes not imply x<=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 identity and non-identity of the operands x and y;x.__operator__(y) returns the value NotImplemented except for the special comparison methods __eq__ and __ne__ for which it returns respectively True and False if the operands x and y are respectively identical and non-identical and the value NotImplemented otherwise.