21
import numpy as np
a = np.array([0])
b = np.array([None])
c = np.array([''])
d = np.array([' '])

为什么我们会有这种不一致:

>>> bool(a)
False
>>> bool(b)
False
>>> bool(c)
True
>>> bool(d)
False
4

4 回答 4

8

正如Scalars中所解释的那样,我很确定答案是:

数组标量具有与 ndarrays 相同的属性和方法。[1] 这允许人们在与数组相同的基础上部分处理数组的项目,从而消除混合标量和数组操作时产生的粗糙边缘。

因此,如果调用标量是可以接受的,那么调用shape 数组bool必须是可以接受的,因为它们尽可能是相同的。bool(1,)

而且,虽然我所知道的文档中的任何地方都没有直接说明,但从设计中可以很明显地看出,NumPy 的标量应该像原生 Python 对象一样工作。

所以,这就解释了为什么np.array([0])是虚假的而不是真实的,这就是你最初感到惊讶的地方。


所以,这解释了基础知识。但是案件的具体情况c呢?

首先,请注意您的数组np.array([''])不是一个包含一个 Pythonobject的数组,而是一个包含一个长度为 1 的 NumPy<U1空终止字符串的数组。固定长度字符串值与 Python 字符串具有不同的真实性规则——它们确实不能;对于固定长度的字符串类型,“如果为空则为假”没有任何意义,因为它们永远不会为空。你可以争论 NumPy 是否应该这样设计,但它显然始终遵循该规则,我认为相反的规则在这里不会更容易混淆,只是不同。

但是字符串似乎还有其他奇怪的事情发生。考虑一下:

>>> np.array(['a', 'b']) != 0
True

这不是将<U2字符串与 0 进行元素比较并返回array([True, True])(正如您从 得到的那样np.array(['a', 'b'], dtype=object)),而是进行数组范围的比较并确定没有字符串数组等于 0,这似乎很奇怪……我不确定这是否值得在这里单独回答,甚至是整个单独的问题,但我很确定我不会成为写这个答案的人,因为我不知道这里发生了什么。:)


除了 shape 数组之外, shape(1,)数组的()处理方式相同,但其他任何东西都是 a ValueError,因为否则很容易将数组与andNumPy 无法自动转换为元素操作的其他 Python 运算符一起滥用。

我个人认为与其他数组保持一致比在这里与标量保持一致更有用——换句话说,只需提高一个ValueError. 我还认为,如果在这里与标量一致重要,那么与未装箱的 Python 值一致会更好。换句话说,如果bool(array([v]))bool(array(v))将被允许,它们应该总是返回与 完全相同的东西bool(v),即使这与 不一致np.nonzero。但我可以从另一个角度看待这个论点。

于 2015-05-05T04:09:02.693 回答
8

对于具有一个元素的数组,数组的真值由该元素的真值决定。

要说明的要点是,np.array([''])不是一个包含一个空 Python 字符串的数组。创建这个数组是为了保存每个恰好一个字节的字符串,并且 NumPy 用空字符填充太短的字符串。这意味着数组等于np.array(['\0'])

在这方面,NumPy 与评估bool('\0')True.

事实上,FalseNumPy 数组中唯一的字符串是不包含任何非空白字符('\0'不是空白字符)的字符串。

该布尔评估的详细信息如下所示。


浏览 NumPy 迷宫般的源代码并不总是那么容易,但我们可以在arraytypes.c.src文件中找到控制不同数据类型中的值如何映射到布尔值的代码。这将解释如何确定bool(a)bool(b)和。bool(c)bool(d)

在我们进入该文件中的代码之前,我们可以看到调用bool()NumPy 数组会调用内部_array_nonzero()函数。如果数组为空,我们得到False. 如果有两个或更多元素,我们会得到一个错误。但是如果数组只有一个元素,我们就打一行:

return PyArray_DESCR(mp)->f->nonzero(PyArray_DATA(mp), mp);

现在,PyArray_DESCR是一个包含数组各种属性的结构。f是指向另一个PyArray_ArrFuncs保存数组nonzero函数的结构的指针。换句话说,NumPy 将调用数组自己的特殊nonzero函数来检查那个元素的布尔值。

确定一个元素是否为非零显然取决于元素的数据类型。实现特定类型的非零函数的代码可以在arraytypes.c.src文件的“非零”部分找到。

正如我们所料,浮点数、整数和复数False是否等于零。这说明bool(a)。在对象数组的情况下,None同样会被评估为False因为 NumPy 只是调用PyObject_IsTrue函数。这说明bool(b)

为了理解 and 的结果bool(c)bool(d)我们看到nonzero字符串类型数组的函数被映射到STRING_nonzero函数:

static npy_bool
STRING_nonzero (char *ip, PyArrayObject *ap)
{
    int len = PyArray_DESCR(ap)->elsize; // size of dtype (not string length)
    int i;
    npy_bool nonz = NPY_FALSE;

    for (i = 0; i < len; i++) {
        if (!Py_STRING_ISSPACE(*ip)) {   // if it isn't whitespace, it's True
            nonz = NPY_TRUE;
            break;
        }
        ip++;
    }
    return nonz;
}

(unicode 案例或多或少是相同的想法。)

因此,在具有字符串或 unicode 数据类型的数组中,字符串只有False在它只包含空白字符时才是:

>>> bool(np.array([' ']))
False

对于问题中的数组c,实际上有一个空字符\0填充看似空的字符串:

>>> np.array(['']) == np.array(['\0'])
array([ True], dtype=bool)

STRING_nonzero函数看到这个非空白字符,所以bool(c)True.

如本答案开头所述,这与 Python 对包含单个空字符的字符串的评估一致:bool('\0')也是True.


更新Wim 已通过使仅包含空字符或仅包含空格和空字符的字符串评估为False. 这意味着 NumPy 1.10+ 将看到bool(np.array(['']))is False,这更符合 Python 对“空”字符串的处理。

于 2015-06-14T13:44:49.820 回答
3

现在已经在master中修复了

我认为这是一个错误,并且numpy开发人员同意了,所以这个补丁今天早些时候被合并了。我们应该会在即将发布的 1.10 版本中看到新的行为。

于 2015-06-16T11:08:09.607 回答
2

Numpy 似乎遵循与内置 python ** 相同的转换,在这种情况下,它似乎是因为调用nonzero. 显然len也可以使用,但在这里,这些数组都不是空的(长度0) - 所以这不是直接相关的。请注意,调用bool([False])True根据这些规则返回。

a = np.array([0])
b = np.array([None])
c = np.array([''])

>>> nonzero(a)
(array([], dtype=int64),)
>>> nonzero(b)
(array([], dtype=int64),)
>>> nonzero(c)
(array([0]),)

bool这似乎也与铸造的更多枚举描述一致——你的例子都被明确讨论过。

有趣的是,字符串数组似乎确实存在系统性不同的行为,例如

>>> a.astype(bool)
array([False], dtype=bool)
>>> b.astype(bool)
array([False], dtype=bool)
>>> c.astype(bool)
ERROR: ValueError: invalid literal for int() with base 10: ''

我认为,当 numpy 将某些内容转换为 bool 时,它使用该PyArray_BoolConverter函数,而该函数又只是调用该PyObject_IsTrue函数 --- 即内置 python 使用的完全相同的函数,这就是为什么numpy结果如此一致的原因。

于 2015-05-05T03:52:10.450 回答