这使我感到困惑:
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
我知道我们不允许将nan与==
进行比较,但是与is
应该被允许吗?毕竟,它的工作时,比较南本身?
3条答案
按热度按时间stszievb1#
这不是关于Python
is
运算符的问题,而是关于数组元素的索引或拆箱的问题:a[2]
不只是返回nan
。它返回一个np.float64
对象,其值为np.nan
。另一个a[2]
将生成另一个np.float64
对象。两个这样的对象在is
意义上不匹配。对于任何数组元素都是如此,而不仅仅是nan
值。由于
==
不适用于nan
,因此我们只能使用np.isnan
函数。np.nan
是唯一的float
对象(在此会话中),但a[2]
未设置为该对象。如果数组被定义为对象类型:
这里
is
是True -因为b
包含指向内存中已经存在的对象的指针,包括np.nan
对象。对于这样构造的列表也是如此。fcy6dtqo2#
首先,至少在NumPy 1.15中,
np.nan
恰好是一个特殊的单例,这意味着每当NumPy必须给予一个类型为float
的NaN值时,它都会尝试给你给予相同的np.nan
值。但这并没有在任何地方被记录下来,也不能保证在不同版本中都是如此。
作为实现细节,这适合更大的值类,这些值可能是也可能不是单例。
作为一般规则,如果您的代码依赖于不可变类型的两个相等值相同或不相同,则代码是错误的。
以下是CPython 3.7默认版本的一些示例:
你可以学习所有这些规则,但是它们都可以在不同的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
。所以,当你要求
a[2]
时,NumPy不可能给予np.nan
。相反,它给你一个值为NaN的np.float64
。这就是为什么
a[2] is np.nan
总是False。但是为什么a[2] is a[2]
通常也是false?正如我上面提到的,NumPy试图在需要给你一个
float
NaN时给予你一个np.nan
。但是,至少在1.15中,当它需要给予np.float64
NaN时,它没有提供任何特殊的单例值。没有理由它不能,但是没有人费心去写这样的代码,因为对于任何正确编写的应用程序来说,这两种方式都不重要。因此,每次将
a[2]
中的值拆箱为标量np.float64
时,它都会给您一个新的NaN值np.float64
。但为什么这与
301-1 is 300
不同呢?好吧,这样做的原因是允许编译器将已知的不可变类型的常量与相等的值合并,CPython在每个编译单元中对简单的情况做了同样的事情。但两个NaN值并不相等; NaN值甚至不等于它本身。所以,它不可能是恒定折叠的。(If你想知道如果你创建一个int dtype数组,并在其中存储小值,并检查它们是否合并到small-int singletons中,会发生什么-试试看。
当然,这就是为什么
isnan
首先存在。你不能用相等性测试NaN(因为NaN的值不等于任何东西,甚至不等于它们自己),你不能用恒等性测试NaN(出于上面描述的所有原因),所以你需要一个函数来测试它们。e3bfsja23#
看看这个:
并且还
他们不一样