'y' (booleano) vs 'y' (bit a bit) - ¿Por qué diferencia en el comportamiento con listas vs matrices numpy?

142

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

Estoy confundido sobre el uso apropiado de &vs anden Python, ilustrado 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 estoy luchando por usar esa información para dar sentido a mis 4 ejemplos anteriores.

El ejemplo 4 me llevó a mi salida deseada, por lo que está bien, pero todavía estoy confundido acerca de cuándo / cómo / por qué debería usar and vs &. ¿Por qué las listas y las matrices NumPy se comportan de manera diferente con estos operadores?

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

rysqui
fuente
2
En Numpy hay np.bitwise_and()y np.logical_and()amigos para evitar confusiones.
Dietrich
1
En el ejemplo 1, mylist1 and mylist2no genera el mismo resultado que mylist2 and mylist1, ya que lo que se devuelve es la segunda lista como lo señala delnan.
user2015487
1
Posible duplicado de Python: operadores booleanos vs operadores Bitwise
Oliver Ni

Respuestas:

113

andprueba si ambas expresiones son lógicamente Truemientras &(cuando se usa con True/ Falsevalores) prueba si ambas lo son True.

En Python, los objetos incorporados vacíos generalmente se tratan como lógicamente, Falsemientras que los incorporados no vacíos son lógicamente 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 la lista 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 True, lógicamente , por lo que el valor de verdad de andla misma es el mismo que el de la segunda lista. (En nuestro caso, la segunda lista no está vacía y, por lo tanto True, lógicamente , pero identificar eso requeriría un paso innecesario de cálculo).

Por ejemplo 2, las listas no pueden combinarse significativamente de manera bit a bit porque pueden contener elementos arbitrarios diferentes. Las cosas que se pueden combinar a nivel bit incluyen: Verdades y Falsas, enteros.

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

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

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

Línea de fondo

  • Si no está tratando con matrices y no está realizando manipulaciones matemáticas de enteros, probablemente lo desee and.

  • Si tiene vectores de valores de verdad que desea combinar, use numpycon &.

ramcdougal
fuente
26

Acerca de list

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

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

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

Acerca de and

andno es un operador (puede llamarlo "operador", pero también puede 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 forma de que un método mantenga una evaluación de algunos de sus operandos, pero andpuede (y debe) hacer eso.

La consecuencia de eso es que andno se puede sobrecargar, al igual forque no se puede sobrecargar. Es completamente general y se comunica a través de un protocolo específico. Lo que puede hacer es personalizar su parte del protocolo, pero eso no significa que pueda alterar el comportamiento deand completo. El protocolo es:

Imagine a Python interpretando "ayb" (esto no sucede literalmente de esta manera, pero ayuda a comprender). Cuando se trata de "y", observa 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(omite b por completo, no se evalúa en absoluto y) dice: aes mi resultado ( NO : Falso es mi resultado).

Si ano responde, andpregunta: ¿cuál es su longitud? (Nuevamente, puede personalizar esto como autor de ala clase). Si la arespuesta es 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 su longitud"), o no responde en absoluto, o responde "sí" a la primera ("¿es verdad?"), andEvalúa b, y dice: bes mi resultado. Tenga en cuenta que lo hace NO preguntar ba cualquier 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 unos minutos con un bolígrafo y papel, y convénzase que cuando {a, b} es un subconjunto de {Verdadero, Falso}, funciona exactamente como cabría esperar 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 entienda su ejemplo 1. andno le importa si mylist1 es un número, lista, 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 elementos de mylist1 y mylist2: no ingresan a la imagen en ningún lugar.

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 bit a bit "y", y bool lo define como lógico "y", pero esa es solo una opción: por ejemplo, los conjuntos y algunos otros objetos, como las vistas de teclas dict, lo definen como una intersección de conjuntos. listsimplemente no lo define, probablemente porque Guido no pensó en ninguna forma obvia de definirlo.

numpy

Por otro lado: -D, las matrices numpy son especiales, o al menos están tratando de serlo. Por supuesto, numpy.array es solo una clase, no puede anular andde ninguna manera, por lo que hace la siguiente mejor cosa: cuando se le pregunta "¿es verdad?", Numpy.array plantea un ValueError, efectivamente diciendo "por favor reformule la pregunta, mi la visión de la verdad no se ajusta a su modelo ". (Tenga en cuenta que el mensaje de ValueError no habla 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 lo desee, y se define de manera &consistente con otros operadores: pointwise. Entonces finalmente obtienes lo que quieres.

HTH

Veky
fuente
23

Los operadores booleanos de cortocircuito ( and, or) no se pueden anular porque no hay una forma satisfactoria de hacerlo sin introducir nuevas características del lenguaje o sacrificar el cortocircuito. Como puede saber 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 evaluar el) operando real, no el valor de verdad del mismo.

La única forma de personalizar su comportamiento es anular __nonzero__(renombrado __bool__en Python 3), para que pueda afectar qué operando se devuelve, pero no devolver algo diferente. Las listas (y otras colecciones) se definen como "verdaderas" 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, son comunes dos nociones diferentes de verdad: (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 explícitamente use .any()o .all().

&y |(y not, por cierto) se puede anular por completo, ya que no provocan cortocircuitos. Pueden reemplazar cualquier cosa cuando se reemplazan, y NumPy hace un buen uso de eso para realizar operaciones basadas en elementos, como lo hacen prácticamente con cualquier otra operación escalar. Las listas, por otro lado, no transmiten operaciones a través de sus elementos. Así como mylist1 - mylist2no significa nada y mylist1 + mylist2significa algo completamente diferente, no hay &operador para las listas.


fuente
3
Un ejemplo particularmente interesante de lo que esto puede producir es [False] or [True]evaluar [False]y [False] and [True]evaluar [True].
Rob Watts
16

Ejemplo 1:

Así es como funciona el operador y .

x e y => si x es falso, entonces x , de lo contrario y

En otras palabras, dado mylist1que no lo es False, el resultado de la expresión es mylist2. (Solo se evalúan las listas vacíasFalse ).

Ejemplo 2

El &operador es por un bit a bit y, como mencionas. Las operaciones bit a bit solo funcionan en números. El resultado de a & b es un número compuesto por 1s en bits que son 1 en a y b . Por ejemplo:

>>> 3 & 1
1

Es más fácil ver lo que sucede usando un literal binario (los mismos números que los anteriores):

>>> 0b0011 & 0b0001
0b0001

Las operaciones bit a bit son similares en concepto a las operaciones booleanas (verdad), pero solo funcionan en bits.

Entonces, dado un par de declaraciones sobre mi auto

  1. Mi coche es rojo
  2. Mi coche tiene ruedas

El "y" lógico de estas dos declaraciones es:

(¿mi automóvil está rojo?) y (¿el automóvil tiene ruedas?) => verdadero lógico de valor falso

Ambos son ciertos, al menos para mi auto. Entonces, el valor de la declaración como un todo es lógicamente verdadero.

La "y" bit a bit de estas dos declaraciones es un poco más nebulosa:

(el valor numérico de la declaración 'mi auto es rojo') y (el valor numérico de la declaración 'mi auto tiene ruedas') => número

Si python sabe cómo convertir las declaraciones a valores numéricos, lo hará y calculará los dos valores a nivel de bit. Esto puede llevarlo a creer que &es intercambiable con and, pero como con el ejemplo anterior, son cosas diferentes. Además, para los objetos que no se pueden convertir, solo obtendrá un TypeError.

Ejemplos 3 y 4:

Numpy implementa operaciones aritméticas para matrices:

Las operaciones aritméticas y de comparación en ndarrays se definen como operaciones basadas en elementos, y generalmente producen objetos ndarray como resultados.

Pero no implementa operaciones lógicas para matrices, porque no puede sobrecargar operadores lógicos en python . Es por eso que el ejemplo tres no funciona, pero el ejemplo cuatro sí.

Entonces, para responder a su pregunta andvs &: Use and.

Las operaciones bit a bit se utilizan para examinar la estructura de un número (qué bits están establecidos, qué bits no están establecidos). Este tipo de información se usa principalmente en interfaces de sistema operativo de bajo nivel ( bits de permiso de Unix , por ejemplo). La mayoría de los programas de Python no necesitarán saber eso.

Las operaciones lógicas ( and, or, not), sin embargo, se utilizan todo el tiempo.

Seth
fuente
14
  1. En Python, una expresión de X and Yretornos Y, dado que bool(X) == Trueo cualquiera de Xo Yevaluar a False, por ejemplo:

    True and 20 
    >>> 20
    
    False and 20
    >>> False
    
    20 and []
    >>> []
  2. El operador bit a bit simplemente no está definido para las listas. Pero se define para enteros, que operan sobre la representación binaria de los números. Considere 16 (01000) y 31 (11111):

    16 & 31
    >>> 16
  3. NumPy no es un psíquico, no sabe si quiere decir que, por ejemplo, [False, False]debería ser igual Trueen una expresión lógica. En esto, anula un comportamiento estándar de Python, que es: "Cualquier colección vacía con len(collection) == 0is False".

  4. Probablemente un comportamiento esperado de las matrices y el operador de NumPy.

Zaur Nasibov
fuente
Falso y 20 vueltas Falso
Rahul
4

Para el primer ejemplo y base en el documento de django
Siempre devolverá la segunda lista, de hecho, una lista no vacía se ve como un valor Verdadero para Python, por lo tanto, Python devuelve el 'último' valor Verdadero para que la segunda lista

In [74]: mylist1 = [False]
In [75]: mylist2 = [False, True, False,  True, False]
In [76]: mylist1 and mylist2
Out[76]: [False, True, False, True, False]
In [77]: mylist2 and mylist1
Out[77]: [False]
MoiTux
fuente
4

Las operaciones con una lista de Python operan en la lista . list1 and list2comprobará si list1está vacío y devolverá list1si lo está y list2si no lo está. list1 + list2anexará list2a list1, por lo que obtener una nueva lista conlen(list1) + len(list2) elementos.

Los operadores que solo tienen sentido cuando se aplican por elementos, como &, plantean unTypeError , ya que las operaciones por elementos no son compatibles sin recorrer los elementos.

Las matrices Numpy admiten operaciones basadas en elementos . array1 & array2calculará el bit a bit o para cada elemento correspondiente en array1y array2. array1 + array2calculará la suma de cada elemento correspondiente en array1y array2.

Esto no funciona para andy or.

array1 and array2 es esencialmente una abreviatura para el siguiente código:

if bool(array1):
    return array2
else:
    return array1

Para esto necesitas una buena definición de bool(array1). Para operaciones globales como las que se usan en las listas de Python, la definición es bool(list) == Truesi listno está vacío y Falsesi está vacío. Para las operaciones con elementos de Numpy, existe cierta desambiguación entre verificar si algún elemento se evalúa Trueo si todos los elementos se evalúan True. Debido a que ambos son posiblemente correctos, numpy no adivina y genera un ValueErrorcuándo se bool()llama (indirectamente) en una matriz.

knbk
fuente
0

Buena pregunta. Similar a la observación que tiene sobre los ejemplos 1 y 4 (o debería decir 1 y 4 :)) sobre operadores lógicos andbit a bit &, experimenté en sumoperador. El numpy sumy el py también se sumcomportan de manera diferente. Por ejemplo:

Supongamos que "mat" es una matriz nudosa de 5x5 2d como:

array([[ 1,  2,  3,  4,  5],
       [ 6,  7,  8,  9, 10],
       [11, 12, 13, 14, 15],
       [16, 17, 18, 19, 20],
       [21, 22, 23, 24, 25]])

Entonces numpy.sum (mat) da la suma total de toda la matriz. Mientras que la suma incorporada de Python como la suma (mat) totaliza solo a lo largo del eje. Vea abajo:

np.sum(mat)  ## --> gives 325
sum(mat)     ## --> gives array([55, 60, 65, 70, 75])
SSPrabhu
fuente