这让我很困惑:
```
a=np.array([1,2,np.nan,3]) # an array with a nan
print(np.isnan(a)[2]) # it truly is a nan
print(a[2]) # it quacks like a nan
print(np.nan is np.nan) # nan's can be compared
print(a[2] is np.nan) # But then, this isn't a nan after all!!??
>>> True
>>> nan
>>> True
>>> False
```
Run Code Online (Sandbox Code Playgroud)
我知道我们不允许比较nan的==
,但is
应该被允许吗?毕竟它在比较nan与自身时有效吗?
感谢您对此处发生的任何提示.
这不是关于Python is
运算符的问题,而是关于数组元素的索引或取消装箱的问题:
In [363]: a=np.array([1,2,np.nan,3])
In [364]: a[2]
Out[364]: nan
In [365]: type(a[2])
Out[365]: numpy.float64
In [366]: a[2] is a[2]
Out[366]: False
Run Code Online (Sandbox Code Playgroud)
a[2]
不仅仅是回归nan
.它返回一个np.float64
值为的对象np.nan
.另一个a[2]
将产生另一个np.float64
对象.在这个is
意义上,两个这样的对象不匹配.对于任何数组元素都是如此,而不仅仅是nan
值.
由于==
不起作用nan
,我们坚持使用该np.isnan
功能.
np.nan
是一个唯一的float
对象(在此会话中),但未a[2]
设置为该对象.
如果数组被定义为对象类型:
In [376]: b=np.array([1,2,np.nan,3], object)
In [377]: b[2] is np.nan
Out[377]: True
Run Code Online (Sandbox Code Playgroud)
这里is
是True - 因为b
包含指向已经存在于内存中的np.nan
对象的指针,包括对象.对于像这样构造的列表也是如此.
首先,至少在 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
Run Code Online (Sandbox Code Playgroud)
您可以了解使所有这些事情以这种方式出现的所有规则,但它们都可以在不同的 Python 实现中更改,或者在版本 3.8 中,甚至在使用自定义配置选项构建的 3.7 中。所以,只是从不1
或math.nan
或np.nan
或''
与is
; 仅将它用于专门记录为单例的对象(None
当然,例如— 或您自己类型的实例)。
其次,当你索引一个 numpy 数组时,它必须通过构造一个适合数组的dtype
. 对于dtype=float64
数组,它构造的标量值是 a np.float64
。
所以,a[2]
保证是一个np.float64
.
但np.nan
不是np.float64
,而是float
。
因此,np.nan
当您要求a[2]
. 相反,它为您提供np.float64
了一个 NaN 值。
好的,这就是为什么a[2] is np.nan
总是 False的原因。但为什么a[2] is a[2]
通常也是假的?
正如我上面提到的,NumPy 会np.nan
在需要给你一个float
NaN时尝试给你。但是——至少在 1.15 中——它没有任何特殊的单例值可以在需要给你一个np.float64
NaN 时提供。没有理由不能,但没有人愿意编写这样的代码,因为这对于任何正确编写的应用程序来说都无关紧要。
因此,每次将值拆箱a[2]
为 scalar 时np.float64
,它都会为您提供一个新的 NaN 值np.float64
。
但为什么这不一样301-1 is 300
?嗯,有效的原因是允许编译器将已知的不可变类型的常量折叠为相等的值,而 CPython 在每个编译单元中就简单的情况做到了这一点。但是两个 NaN 值不相等;NaN 值甚至不等于自身。所以,它不能被恒定折叠。
(如果你想知道如果你用 int dtype 创建一个数组并在其中存储小值并检查它们是否合并到 small-int 单例中会发生什么 - 试试看。)
当然,这就是isnan
首先存在的原因。你不能用相等来测试 NaN(因为 NaN 值不等于任何东西,甚至它们自己),你不能用身份测试 NaN(出于上述所有原因),所以你需要一个函数来测试他们。