Во-первых, по крайней мере в NumPy 1.15, np.nan
оказывается специальным синглтоном, что означает, что всякий раз, когда NumPy должен дать вам значение NaN типа float
, он пытается дать вам то же значение np.nan
.
Но это нигде не задокументировано или гарантированно будет верным во всех версиях.
Это вписывается в больший класс значений, которые могут быть, а могут и не быть одиночными, как подробности реализации.
Как правило, если ваш код основан на двух одинаковых значениях неизменяемого типа, которые идентичны или не идентичны, ваш код неверен.
Вот несколько примеров из сборки CPython 3.7 по умолчанию:
>>> a, b = 200, 201
>>> a is b-1
True
>>> a, b = 300, 301
>>> a is b-1
False
>>> 301-1 is 300
True
>>> math.nan is math.nan
True
>>> float('nan') is math.nan
False
>>> float('nan') is float('nan')
False
Вы можете изучить все правила, которые делают все эти вещи такими, но все они могут измениться в другой реализации Python, или в версии 3.8, или даже в 3.7, созданной с помощью пользовательских настроить параметры. Так что просто никогда 1
или math.nan
, или np.nan
, или ''
с is
; используйте его только для объектов, которые задокументированы как одиночные (например, None
- или экземпляры ваших собственных типов, конечно).
Во-вторых, когда вы индексируете массив numpy, он должен «распаковать» значение, создав скаляр типа, соответствующего массиву dtype
. Для массива dtype=float64
его скалярное значение равно np.float64
.
Итак, a[2]
гарантированно будет np.float64
.
Но np.nan
это не np.float64
, это float
.
Итак, NumPy не может дать вам np.nan
, когда вы попросите a[2]
. Вместо этого он дает np.float64
со значением NaN.
ОК, поэтому a[2] is np.nan
всегда ложно. Но почему a[2] is a[2]
также обычно ложно?
Как я упоминал выше, NumPy пытается дать вам np.nan
всякий раз, когда ему нужно дать вам float
NaN. Но - по крайней мере, в 1.15 - он не имеет какого-либо специального одноэлементного значения, чтобы предоставить его, когда ему нужно дать np.float64
NaN. Нет причины, по которой не смог бы , но никто не удосужился написать такой код, потому что это не должно иметь никакого значения для любого правильно написанного приложения.
Итак, каждый раз, когда вы распаковываете значение в a[2]
в скаляр np.float64
, он дает вам новое значение NaN np.float64
.
Но почему это не то же самое, что 301-1 is 300
? Хорошо, причина, по которой это работает, заключается в том, что компилятору разрешено складывать константы известного неизменяемого типа с равными значениями, а CPython делает именно это, для простых случаев, внутри каждой единицы компиляции. Но два значения NaN не равны; значение NaN даже не равно самому себе. Таким образом, он не может быть сложен постоянно.
(Если вам интересно, что произойдет, если вы создадите массив с типом int d, сохраните в нем небольшие значения и проверите, объединяются ли они в синглоны small-int, попробуйте и посмотрите.)
И, конечно, именно поэтому isnan
существует в первую очередь. Вы не можете проверить NaN с равенством (потому что значения NaN не равны ни с чем, даже сами по себе), вы не можете проверить NaN с идентичностью (по всем причинам, описанным выше), поэтому вам нужна функция для проверки им.