5

Pretty much anyone who works with IEEE floating-point values has run into NaN, or "not a number", at some point. Famously, NaN is not equal to itself.

>>> x = float('nan')
>>> x == x
False

Now, I had come to terms with this, but there's a strange behavior I'm struggling to wrap my head around. Namely,

>>> x in [x]
True

I had always assumed that list.__contains__ was written something like

def __contains__(self, element):
    for x in self:
        if element == x:
            return True
    return False

i.e., it used __eq__ on the relevant data type internally. And indeed it does. If I define a custom class with an __eq__ method of my own design, then I can verify that Python does in fact call __eq__ when doing the inclusion check. But then how can there exist a value x (NaN in our case) such that x == x is false but x in [x] is true?

We can observe the same behavior with a custom __eq__ as well.

class Example:

    def __eq__(self, other):
        return False

x = Example()
print(x == x)   # False
print(x in [x]) # True
Silvio Mayolo
  • 62,821
  • 6
  • 74
  • 116

1 Answers1

5

According to the docs it first uses the is operator to check for equality, and since x is x is True, x in [x] is also True:

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

Note that identity (is) is different from equality (==). Also note that not all NaN values are represented by the same object, so if you try your test with two different NaN objects:

>>> float('nan') in [float('nan')]
False

you'll see different results.

user2357112
  • 260,549
  • 28
  • 431
  • 505
Selcuk
  • 57,004
  • 12
  • 102
  • 110