python - El valor de verdad de la matriz numpy con un elemento falsey parece depender de dtype
arrays types (4)
import numpy as np
a = np.array([0])
b = np.array([None])
c = np.array([''''])
d = np.array(['' ''])
¿Por qué deberíamos tener esta inconsistencia?
>>> bool(a)
False
>>> bool(b)
False
>>> bool(c)
True
>>> bool(d)
False
Está arreglado en el maestro ahora.
Pensé que esto era un error, y los desarrolladores de numpy
estaban de acuerdo, por lo que este parche se fusionó el día de hoy. Deberíamos ver nuevos comportamientos en la próxima versión 1.10.
Estoy bastante seguro de que la respuesta es, como se explica en Scalars , que:
Los escalares de matrices tienen los mismos atributos y métodos que los ndarrays. [1] Esto permite tratar elementos de una matriz en parte sobre la misma base que las matrices, suavizando los bordes ásperos que se producen al mezclar operaciones escalares y de matriz.
Entonces, si es aceptable llamar a bool
en un escalar, debe ser aceptable llamar a bool
en una matriz de forma (1,)
, porque son, en la medida de lo posible, lo mismo.
Y, aunque no se dice directamente en ninguna parte de los documentos que conozco, es bastante obvio por el diseño que se supone que los escalares de NumPy deben actuar como objetos nativos de Python.
Entonces, eso explica por qué np.array([0])
es falsey en lugar de verdad, que es lo que inicialmente te sorprendió.
Entonces, eso explica lo básico. Pero ¿qué pasa con los detalles del caso c
?
Primero, tenga en cuenta que su matriz np.array([''''])
no es una matriz de un object
Python, sino una matriz de una cadena de caracteres NumPy <U1
terminada en nulo de longitud 1. Los valores de cadena de longitud fija no tienen la misma regla de la verdad que las cuerdas de Python, y realmente no pudieron; para un tipo de cadena de longitud fija, "falso si está vacío" no tiene ningún sentido, porque nunca están vacíos. Podría discutir si NumPy debería haber sido diseñado de esa manera o no, pero claramente sigue esa regla de manera consistente, y no creo que la regla opuesta sea menos confusa aquí, solo que diferente.
Pero parece que hay algo más extraño con las cuerdas. Considera esto:
>>> np.array([''a'', ''b'']) != 0
True
Eso no es hacer una comparación elemental de las <U2
cadenas a 0 y devolver la array([True, True])
(como obtendría de np.array([''a'', ''b''], dtype=object)
), está haciendo una comparación de toda la matriz y decidiendo que ninguna matriz de cadenas es igual a 0, lo que parece extraño ... No estoy seguro de si esto merece una respuesta por separado aquí o incluso una pregunta por completo, pero estoy bastante seguro de que estoy No voy a ser el que escriba esa respuesta, porque no tengo ni idea de lo que está pasando aquí. :)
Más allá de las matrices de shape (1,)
, las matrices de shape ()
se tratan de la misma manera, pero cualquier otra cosa es un ValueError
, porque de lo contrario sería muy fácil hacer mal uso de las matrices con y otros operadores de Python que NumPy no puede convertir automáticamente. en operaciones elementwise.
Personalmente, creo que ser coherente con otras matrices sería más útil que ser coherente con los escalares aquí; en otras palabras, solo tiene que crear un ValueError
. También creo que, si la coherencia con los escalares fuera importante aquí, sería mejor ser coherente con los valores de Python sin caja. En otras palabras, si se va a permitir el uso de bool(array([v]))
y bool(array(v))
, siempre se debe devolver exactamente lo mismo que bool(v)
, incluso si eso no es consistente con np.nonzero
. Pero puedo ver el argumento de otra manera.
Para las matrices con un elemento, el valor de verdad de la matriz está determinado por el valor de verdad de ese elemento.
El punto principal que se debe hacer es que np.array([''''])
no es una matriz que contiene una cadena de Python vacía. Esta matriz se crea para contener cadenas de exactamente un byte cada una y las cadenas NumPy pads que son demasiado cortas con el carácter nulo. Esto significa que la matriz es igual a np.array([''/0''])
.
En este sentido, NumPy está siendo consistente con Python, que evalúa bool(''/0'')
como True
.
De hecho, las únicas cadenas que son False
en las matrices NumPy son cadenas que no contienen ningún carácter que no sea un espacio en blanco ( ''/0''
no es un carácter de espacio en blanco).
Los detalles de esta evaluación booleana se presentan a continuación.
Navegar por el código fuente laberíntico de NumPy no siempre es fácil, pero podemos encontrar el código que rige cómo los valores en diferentes tipos de datos se asignan a valores booleanos en el archivo arraytypes.c.src . Esto explicará cómo se determinan bool(a)
, bool(b)
, bool(c)
y bool(d)
.
Antes de llegar al código en ese archivo, podemos ver que llamar a bool()
en una matriz NumPy invoca la función interna _array_nonzero()
. Si la matriz está vacía, obtenemos False
. Si hay dos o más elementos obtenemos un error. Pero si la matriz tiene exactamente un elemento, golpeamos la línea:
return PyArray_DESCR(mp)->f->nonzero(PyArray_DATA(mp), mp);
Ahora, PyArray_DESCR
es una estructura que contiene varias propiedades para la matriz. f
es un puntero a otra estructura PyArray_ArrFuncs
que contiene la función nonzero
la matriz. En otras palabras, NumPy recurrirá a la función especial nonzero
la matriz para verificar el valor booleano de ese elemento.
Determinar si un elemento es distinto de cero o no dependerá obviamente del tipo de datos del elemento. El código que implementa las funciones distintas de cero específicas del tipo se puede encontrar en la sección "distinta de cero" del archivo arraytypes.c.src .
Como es de esperar, los números flotantes, enteros y complejos son False
si son iguales a cero . Esto explica bool(a)
. En el caso de matrices de objetos, None
se evaluará de forma similar como False
porque NumPy simplemente llama a la función PyObject_IsTrue
. Esto explica bool(b)
.
Para entender los resultados de bool(c)
y bool(d)
, vemos que la función nonzero
para las matrices de tipo cadena se asigna a la función 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;
}
(El caso de Unicode es más o menos la misma idea).
Por lo tanto, en matrices con una cadena o un tipo de datos Unicode, una cadena solo es False
si solo contiene caracteres de espacio en blanco:
>>> bool(np.array(['' '']))
False
En el caso de la matriz c
en la pregunta, realmente hay un carácter nulo /0
rellena la cadena aparentemente vacía:
>>> np.array(['''']) == np.array([''/0''])
array([ True], dtype=bool)
La función STRING_nonzero
ve este carácter no en espacios en blanco y, por lo tanto, bool(c)
es True
.
Como se señaló al comienzo de esta respuesta, esto es consistente con la evaluación de Python de cadenas que contienen un solo carácter nulo: bool(''/0'')
también es True
.
Actualización : Wim ha corregido el comportamiento detallado anteriormente en la rama maestra de NumPy haciendo cadenas que solo contienen caracteres nulos, o una combinación de solo espacios en blanco y caracteres nulos, evalúe en False
. Esto significa que NumPy 1.10+ verá que bool(np.array(['''']))
es False
, que está mucho más en línea con el tratamiento de Python de cadenas "vacías".
Numpy parece estar siguiendo los mismos lanzamientos que Python incorporado **, en este contexto parece ser debido a que devuelve true para llamadas a un valor nonzero
de nonzero
. Aparentemente, también se puede usar len
, pero aquí, ninguna de estas matrices está vacía (longitud 0
), por lo que no es directamente relevante. Tenga en cuenta que llamar a bool([False])
también devuelve True
acuerdo con estas reglas.
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]),)
Esto también parece ser coherente con la descripción más enumeración de bool
casting --- donde todos los ejemplos se discuten explícitamente.
Curiosamente, parece que hay un comportamiento sistemáticamente diferente con las matrices de cadenas, por ejemplo,
>>> 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: ''''
Creo que cuando Numpy convierte algo en un bool usa la función PyArray_BoolConverter
que, a su vez, simplemente llama a la función PyObject_IsTrue
, es decir, la misma función que utiliza Python, por lo que los resultados de numpy
son tan consistentes.