Подтвердить что ты не робот

Значение истины массива numpy с одним элементом ложности, по-видимому, зависит от типа dtype

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
4b9b3361

Ответ 1

Для массивов с одним элементом значение истины массива определяется значением истины этого элемента.

Главное, что np.array(['']) не является массивом, содержащим одну пустую строку Python. Этот массив создается для хранения строк по одному байту, а NumPy - строки, которые слишком коротки, с нулевым символом. Это означает, что массив равен np.array(['\0']).

В этом отношении NumPy согласуется с Python, который оценивает bool('\0') как True.

Фактически единственными строками, которые являются False в массивах NumPy, являются строки, которые не содержат никаких символов без пробелов ('\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).

Чтобы понять результаты 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 True
            nonz = NPY_TRUE;
            break;
        }
        ip++;
    }
    return nonz;
}

(Случай с unicode более или менее похож на идею.)

Таким образом, в массивах с типом данных типа string или 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 установил поведение, подробно описанное выше в ветке мастера NumPy, создав строки, содержащие только нулевые символы, или сочетание только пробельные и пустые символы, оцените False. Это означает, что NumPy 1.10+ увидит, что bool(np.array([''])) - False, что намного больше соответствует обработке Python "пустых" строк.

Ответ 2

Я уверен, что ответ, как описано в Scalars, что:

Массивные скаляры имеют те же атрибуты и методы, что и ndarrays. [1] Это позволяет обрабатывать элементы массива частично на том же основании, что и массивы, сглаживая грубые грани, возникающие при смешивании скалярных и массивных операций.

Итак, если это приемлемо для вызова bool на скаляре, должно быть приемлемо вызвать bool в массиве формы (1,), поскольку они, насколько это возможно, одно и то же.

И, хотя это прямо не сказано нигде в документах, о которых я знаю, это довольно очевидно из того, что сканеры NumPy должны действовать как собственные объекты Python.

Итак, это объясняет, почему np.array([0]) является ложным, а не правдоподобным, что и было изначально удивлено.


Итак, это объясняет основы. Но как насчет особенностей случая c?

Во-первых, обратите внимание, что ваш массив np.array(['']) не является массивом одного Python object, а массивом из одной символьной строки NumPy <U1 с нулевым символом длины 1. Значения строки фиксированной длины не имеют такое же правило правды, как и строки Python, - и они действительно не могли; для типа с фиксированной длиной строки "false if empty" не имеет никакого смысла, потому что они никогда не пусты. Вы могли бы рассуждать о том, должен ли NumPy быть спроектирован таким образом или нет, но он явно придерживается этого правила последовательно, и я не думаю, что противоположное правило было бы менее запутанным здесь, просто другим.

Но, похоже, что-то странное происходит со строками. Рассмотрим это:

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

Это не делает поэлементное сравнение строк <U2 с 0 и возвращает array([True, True]) (как вы бы получили из np.array(['a', 'b'], dtype=object)), он проводит сравнение по всему массиву и решает, что ни один массив строк не равен к 0, что кажется странным... Я не уверен, что это заслуживает отдельного ответа здесь или даже целого отдельного вопроса, но я уверен, что я не собираюсь быть тем, кто пишет этот ответ, потому что я понятия не имею что происходит здесь.:)


Помимо массивов формы (1,), массивы формы () обрабатываются одинаково, но что-либо еще является ValueError, потому что в противном случае было бы очень легко злоупотреблять массивами с помощью and и других операторов Python что NumPy не может автоматически преобразовать в элементарные операции.

Я лично считаю, что совместимость с другими массивами была бы более полезной, чем согласование со скалярами здесь - другими словами, просто поднимите ValueError. Я также считаю, что если бы согласование со скалярами было важно здесь, лучше было бы соответствовать несохраненным значениям Python. Другими словами, если bool(array([v])) и bool(array(v)) вообще будут разрешены, они должны всегда возвращать точно то же самое, что и bool(v), даже если это не соответствует np.nonzero. Но я вижу аргумент другим способом.

Ответ 4

Как представляется, Numpy придерживается тех же самых отливок, что и встроенный python **, в этом контексте похоже, из-за чего возвращается true для вызовов 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 casting --- где ваши примеры явно обсуждаются.

Интересно, что, как представляется, систематически различается поведение со строковыми массивами, например.

>>> 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 настолько согласованы.