Membership testing
Two different instances of float('nan')
are not equal to each other. They are "Not a Number" so it makes sense that they shouldn't also have to be equal. They are different instances of objects which are not numbers:
print(float('nan') == float('nan')) # False
As documented here:
For container types such as list, tuple, set, frozenset, dict, or
collections.deque, the expression x in y is equivalent to any(x is e
or x == e for e in y).
There is a checking for identity! that's why you see that behavior in your question and why NaN in a
returns True
and float('nan') in a
doesn't.
Sorting in Python
Python uses the Timsort algorithm for its sorted()
function. (Also see this for a textual explanation.) I'm not going to go into that. I just want to demonstrate a simple example:
This is my class A
. It's going to be our float('nan')
object. It acts like float('nan')
in that it returns False
for all comparison operations:
class A:
def __init__(self, n):
self.n = n
def __lt__(self, other):
print(self, 'lt is calling', other)
return False
def __gt__(self, other):
print(self, 'gt is calling', other)
return False
def __repr__(self):
return f'A({self.n})'
class B:
def __init__(self, n):
self.n = n
def __lt__(self, other):
print(self, 'lt is calling', other)
return False
def __gt__(self, other):
print(self, 'gt is calling', other)
return False
def __repr__(self):
return f'B({self.n})'
When we use the sorted()
function (or the .sort()
method of a list
) without the reverse=True
argument, we're asking for the iterable to be sorted in ascending order. To do this, Python tries to call the __lt__
method successively, starting from the second object in the list to see if it is less than its previous object and so on:
lst = [A(1), B(2), A(3), B(4)]
print(sorted(lst))
output :
B(2) lt is calling A(1)
A(3) lt is calling B(2)
B(4) lt is calling A(3)
[A(1), B(2), A(3), B(4)]
Now, switching back to your example:
lst = [3, A(1), 4, 2, A(1), 1]
print(sorted(lst))
output:
A(1) lt is calling 3
A(1) gt is calling 4
A(1) gt is calling 2
A(1) lt is calling 2
A(1) lt is calling 4
A(1) gt is calling 1
[3, A(1), 1, 2, 4, A(1)]
A(1).__lt__(3)
will return False
. This means A(1)
is not less
than 3 or This means 3
is in correct position relative to A(1)
.
- Then here
int.__lt__(4, A(1))
gets called and because it returns
NotImplemented
object, Python checks to see if A(1)
has
implemented __gt__
and yes, so A(1).__gt__(4)
will return
False
again and this means the A(1)
object is in correct place
relative to 4
.
- (Etc.)
This is why the result of sorted()
seems to be weird, but it's predictable. A(1)
object in both cases, I mean when int
class returns NotImplemented
and when __lt__
gets called from A(1)
, will return False.
It's better to check the Timsort algorithm and consider those points. I would include the remaining steps if I read Timsort algorithm carefully.