'y' (booleano) frente a '&' (bit a bit): ¿Por qué la diferencia de comportamiento entre listas y matrices numerosas?

Resuelto rysqui asked hace 10 años • 8 respuestas

¿Qué explica la diferencia en el comportamiento de las operaciones booleanas y bit a bit en listas frente a matrices NumPy?

Estoy confundido acerca del uso apropiado de &vs anden Python, como se ilustra en los siguientes ejemplos.

mylist1 = [True,  True,  True, False,  True]
mylist2 = [False, True, False,  True, False]

>>> len(mylist1) == len(mylist2)
True

# ---- Example 1 ----
>>> mylist1 and mylist2
[False, True, False, True, False]
# I would have expected [False, True, False, False, False]

# ---- Example 2 ----
>>> mylist1 & mylist2
TypeError: unsupported operand type(s) for &: 'list' and 'list'
# Why not just like example 1?

>>> import numpy as np

# ---- Example 3 ----
>>> np.array(mylist1) and np.array(mylist2)
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
# Why not just like Example 4?

# ---- Example 4 ----
>>> np.array(mylist1) & np.array(mylist2)
array([False,  True, False, False, False], dtype=bool)
# This is the output I was expecting!

Esta respuesta y esta respuesta me ayudaron a comprender que andes una operación booleana pero &es una operación bit a bit.

Leí sobre operaciones bit a bit para comprender mejor el concepto, pero me cuesta usar esa información para darle sentido a los 4 ejemplos anteriores.

El ejemplo 4 me llevó al resultado deseado, así que está bien, pero todavía estoy confundido acerca de cuándo/cómo/por qué debo usar andvs &. ¿Por qué las listas y los arreglos NumPy se comportan de manera diferente con estos operadores?

¿Alguien puede ayudarme a comprender la diferencia entre operaciones booleanas y bit a bit para explicar por qué manejan listas y matrices NumPy de manera diferente?

rysqui avatar Mar 26 '14 04:03 rysqui
Aceptado

andprueba si ambas expresiones son lógicas Truemientras &(cuando se usa con valores True/ False) prueba si ambas son True.

En Python, los objetos integrados vacíos normalmente se tratan de forma lógica, Falsemientras que los objetos integrados no vacíos se tratan de forma lógica True. Esto facilita el caso de uso común en el que desea hacer algo si una lista está vacía y otra cosa si no lo está. Tenga en cuenta que esto significa que la lista [False] es lógicamente True:

>>> if [False]:
...    print('True')
...
True

Entonces, en el Ejemplo 1, la primera lista no está vacía y, por lo tanto, lógicamente True, el valor de verdad de andes el mismo que el de la segunda lista. (En nuestro caso, la segunda lista no está vacía y, por lo tanto, es lógica True, pero identificarla requeriría un paso de cálculo innecesario).

Por ejemplo 2, las listas no se pueden combinar de manera significativa en modo bit a bit porque pueden contener elementos arbitrarios diferentes. Las cosas que se pueden combinar bit a bit incluyen: verdaderos y falsos, números enteros.

Los objetos NumPy, por el contrario, admiten cálculos vectorizados. Es decir, le permiten realizar las mismas operaciones en varios datos.

El ejemplo 3 falla porque las matrices NumPy (de longitud > 1) no tienen valor de verdad, lo que evita la confusión de la lógica basada en vectores.

El ejemplo 4 es simplemente una andoperación de bits vectorizados.

Línea de fondo

  • Si no está trabajando con matrices y no está realizando manipulaciones matemáticas de números enteros, probablemente desee and.

  • Si tiene vectores de valores de verdad que desea combinar, utilícelos numpycon &.

ramcdougal avatar Mar 25 '2014 21:03 ramcdougal

Acerca delist

Primero un punto muy importante, del que se desprenderá todo (espero).

En Python normal, listno es especial de ninguna manera (excepto por tener una linda sintaxis para la construcción, que es principalmente un accidente histórico). Una vez que se crea una lista [3,2,6], es para todos los efectos solo un objeto Python común, como un número 3, un conjunto {3,7}o una función lambda x: x+5.

(Sí, admite el cambio de sus elementos, admite la iteración y muchas otras cosas, pero eso es exactamente lo que es un tipo: admite algunas operaciones, pero no admite otras. int admite elevar a una potencia, pero eso no hazlo muy especial: eso es exactamente lo que es un int. lambda admite llamadas, pero eso no lo hace muy especial: para eso está lambda, después de todo :).

Acerca deand

andno es un operador (puedes llamarlo "operador", pero también puedes llamar "para" un operador :). Los operadores en Python son (implementados mediante) métodos llamados a objetos de algún tipo, generalmente escritos como parte de ese tipo. No hay manera de que un método realice una evaluación de algunos de sus operandos, pero andpuede (y debe) hacerlo.

La consecuencia de eso es que andno se puede sobrecargar, al igual que forno se puede sobrecargar. Es completamente general y se comunica a través de un protocolo específico. Lo que puedes hacer es personalizar tu parte del protocolo, pero eso no significa que puedas alterar el comportamiento por andcompleto. El protocolo es:

Imagine que Python interpreta "a y b" (esto no sucede literalmente de esta manera, pero ayuda a comprender). Cuando llega a "y", mira el objeto que acaba de evaluar (a) y le pregunta: ¿es verdad? ( NO : ¿lo eres True?) Si eres autor de una clase, puedes personalizar esta respuesta. Si aresponde "no", and(se salta b por completo, no se evalúa en absoluto y) dice: aes mi resultado ( NO : Falso es mi resultado).

Si ano responde, andle pregunta: ¿cuál es su longitud? (Nuevamente, puedes personalizar esto como autor de ala clase). Si aresponde 0, andhace lo mismo que arriba: lo considera falso ( NO Falso), omite by da acomo resultado.

Si aresponde algo distinto de 0 a la segunda pregunta ("cuál es tu longitud"), o no responde nada, o responde "sí" a la primera ("eres cierto"), andevalúa b, y dice: bes mi resultado. Tenga en cuenta que NO hace bninguna pregunta.

La otra forma de decir todo esto es que a and bes casi lo mismo que b if a else a, excepto que a se evalúa solo una vez.

Ahora siéntese durante unos minutos con lápiz y papel y convénzase de que cuando {a,b} es un subconjunto de {Verdadero,Falso}, funciona exactamente como esperaría de los operadores booleanos. Pero espero haberte convencido de que es mucho más general y, como verás, mucho más útil de esta manera.

Poniendo esos dos juntos

Ahora espero que entiendas tu ejemplo 1. andNo le importa si mylist1 es un número, una lista, una lambda o un objeto de una clase Argmhbl. Solo le importa la respuesta de mylist1 a las preguntas del protocolo. Y, por supuesto, mylist1 responde 5 a la pregunta sobre la longitud, por lo que devuelve mylist2. Y eso es. No tiene nada que ver con los elementos de mylist1 y mylist2: no entran en escena en ninguna parte.

Segundo ejemplo: &enlist

Por otro lado, &es un operador como cualquier otro, como +por ejemplo. Se puede definir para un tipo definiendo un método especial en esa clase. intlo define como "y" bit a bit, y bool lo define como "y" lógico, pero esa es solo una opción: por ejemplo, los conjuntos y algunos otros objetos como las vistas de claves dict lo definen como una intersección de conjuntos. listsimplemente no lo define, probablemente porque a Guido no se le ocurrió ninguna forma obvia de definirlo.

engordado

En el otro lado: -D, las matrices numerosas son especiales, o al menos intentan serlo. Por supuesto, numpy.array es solo una clase, no puede anularse andde ninguna manera, por lo que hace lo siguiente mejor: cuando se le pregunta "¿es cierto?", numpy.array genera un ValueError, que efectivamente dice "por favor reformule la pregunta, mi visión de la verdad no encaja en su modelo". (Tenga en cuenta que el mensaje ValueError no habla de ello and, porque numpy.array no sabe quién le hace la pregunta; solo habla de la verdad).

Para &, es una historia completamente diferente. numpy.array puede definirlo como desee y lo define &de forma coherente con otros operadores: puntualmente. Entonces finalmente obtienes lo que quieres.

HTH,

Veky avatar Mar 26 '2014 06:03 Veky

Los operadores booleanos de cortocircuito ( and, or) no se pueden anular porque no existe una forma satisfactoria de hacerlo sin introducir nuevas características del lenguaje o sacrificar el cortocircuito. Como puede que sepas o no, evalúan el primer operando por su valor de verdad y, dependiendo de ese valor, evalúan y devuelven el segundo argumento, o no evalúan el segundo argumento y devuelven el primero:

something_true and x -> x
something_false and x -> something_false
something_true or x -> something_true
something_false or x -> x

Tenga en cuenta que se devuelve el (resultado de la evaluación del) operando real, no el valor de verdad del mismo.

La única forma de personalizar su comportamiento es anularlo __nonzero__(renombrarlo __bool__en Python 3), de modo que pueda afectar qué operando se devuelve, pero no devolver algo diferente. Las listas (y otras colecciones) se definen como "veraces" cuando contienen algo y "falsas" cuando están vacías.

Las matrices NumPy rechazan esa noción: para los casos de uso a los que apuntan, dos nociones diferentes de verdad son comunes: (1) si algún elemento es verdadero y (2) si todos los elementos son verdaderos. Dado que estos dos son completamente (y silenciosamente) incompatibles, y ninguno es claramente más correcto o más común, NumPy se niega a adivinar y requiere que uses .any()o explícitamente .all().

&y |(y not, por cierto) se pueden anular por completo, ya que no provocan cortocircuitos. Pueden devolver cualquier cosa cuando se anulan, y NumPy hace un buen uso de eso para realizar operaciones de elementos, como lo hacen con prácticamente cualquier otra operación escalar. Las listas, por otro lado, no transmiten operaciones entre sus elementos. Así como mylist1 - mylist2no significa nada y mylist1 + mylist2significa algo completamente diferente, no existe ningún &operador para las listas.

 avatar Mar 25 '2014 21:03