Search code examples
pythonpylint

Pylint redundant comparison for NaN


Why does Pylint think this is a redundant comparison? Is this not the fastest way to check for NaN?

Refactor: R0124
Redundant comparison - value_1 != value_1
Redundant comparison - value_2 != value_2

How else am I supposed to check if two values are equal including when they're nan?

NaN = float("NaN")

def compare(value_1, value_2):
    match_nan = value_1 != value_1
    print(match_nan and
          value_2 != value_2 or
          value_1 == value_2)

compare(1, 1)
compare(1, NaN)
compare(NaN, 1)
compare(NaN, NaN)

Output:

True
False
False
True

Now, sure math.is_nan is a better solution if you're working with custom classes:

from math import isnan

class NotEQ:
    def __eq__(self, other):
        return False
not_eq = NotEQ()
print(not_eq != not_eq)
print(isnan(not_eq))

Output:

True
... TypeError: must be real number, not NotEQ

I'm writing a JSON patcher, and I don't think the normal behaviour is very useful when you want to be able to remove them from lists, or raise an error is two values aren't equal (but allowing NaN and NaN)


Solution

  • Use math.isnan(x); it's just as fast as x != x. The answer you cited* that says x != x is fastest is using a bad test, and I left a comment there to explain:

    At this scale (nanoseconds) name and attribute lookup time are significant. If you use only local names, the difference between x != x and math.isnan(x) disappears; they're both about 35 ns on my system. You can use %timeit in cell mode to check:

    %%timeit x = float('nan')
    x != x
    
    36 ns ± 0.51 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)
    
    %%timeit x = float('nan'); from math import isnan
    isnan(x)
    
    35.8 ns ± 0.282 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)
    

    * In this comment