'and' (boolean) vs '&' (bit a bit) - Por que diferença de comportamento com listas vs matrizes numpy?


142

O que explica a diferença no comportamento de operações booleanas e bit a bit em listas versus matrizes NumPy?

Estou confuso sobre o uso apropriado de &vs andem Python, ilustrado nos exemplos a seguir.

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 resposta e esta resposta me ajudaram a entender que andé uma operação booleana, mas &é uma operação bit a bit.

Eu li sobre operações bit a bit para entender melhor o conceito, mas estou lutando para usar essas informações para entender meus 4 exemplos acima.

Exemplo 4 me levou à minha saída desejada, de modo que é bom, mas eu ainda estou confuso sobre quando / como / por que eu deveria usar andvs &. Por que listas e matrizes NumPy se comportam de maneira diferente com esses operadores?

Alguém pode me ajudar a entender a diferença entre operações booleanas e bit a bit para explicar por que eles manipulam listas e matrizes NumPy de maneira diferente?


2
Em Numpy há np.bitwise_and()e np.logical_and()e amigos para evitar confusão.
Dietrich

1
No exemplo 1, mylist1 and mylist2não gera o mesmo resultado que mylist2 and mylist1, pois o que está sendo retornado é a segunda lista, conforme apontado por delnan.
User2015487

Respostas:


113

andtesta se ambas as expressões são logicamente Trueenquanto &(quando usado com True/ Falsevalues) testa se ambas são True.

No Python, os objetos internos vazios são normalmente tratados como logicamente, Falseenquanto os internos não vazios são logicamente True. Isso facilita o caso de uso comum em que você deseja fazer algo se uma lista estiver vazia e outra coisa se a lista não estiver. Observe que isso significa que a lista [False] é logicamente True:

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

Portanto, no Exemplo 1, a primeira lista não está vazia e, portanto True, logicamente , portanto, o valor verdadeiro da andé o mesmo que o da segunda lista. (No nosso caso, a segunda lista não está vazia e, portanto True, logicamente , mas identificar isso exigiria uma etapa de cálculo desnecessária.)

Por exemplo 2, as listas não podem ser combinadas significativamente de maneira bit a bit, pois podem conter elementos diferentes arbitrários. As coisas que podem ser combinadas bit a bit incluem: Trues e Falses, números inteiros.

Os objetos NumPy, por outro lado, suportam cálculos vetorizados. Ou seja, eles permitem que você execute as mesmas operações em várias partes de dados.

O exemplo 3 falha porque as matrizes NumPy (de comprimento> 1) não têm valor de verdade, pois isso evita confusão lógica baseada em vetor.

O exemplo 4 é simplesmente uma andoperação de bit vetorizado .

Bottom Line

  • Se você não está lidando com matrizes e não está realizando manipulações matemáticas de números inteiros, provavelmente deseja and.

  • Se você tiver vetores de valores de verdade que deseja combinar, use numpycom &.


26

Sobre list

Primeiro, um ponto muito importante, do qual tudo se seguirá (espero).

No Python comum, listnão é especial de forma alguma (exceto ter uma sintaxe atraente para a construção, que é principalmente um acidente histórico). Depois que uma lista [3,2,6]é feita, ela é, para todos os efeitos, apenas um objeto Python comum, como um número 3, conjunto {3,7}ou função lambda x: x+5.

(Sim, ele suporta a alteração de seus elementos, e suporta iteração e muitas outras coisas, mas isso é exatamente o que é um tipo: suporta algumas operações, enquanto não suporta outras. Int suporta aumentar a potência, mas isso não Torne-o muito especial - é exatamente o que é um int O lambda suporta chamadas, mas isso não o torna muito especial - é para isso que serve o lambda, afinal :).

Sobre and

andnão é um operador (você pode chamá-lo de "operador", mas pode chamar "de" um operador também :). Operadores em Python são (implementados por) métodos chamados em objetos de algum tipo, geralmente escritos como parte desse tipo. Não há como um método realizar uma avaliação de alguns de seus operandos, mas andpode (e deve) fazer isso.

A conseqüência disso é que andnão pode ser sobrecarregado, assim como fornão pode ser sobrecarregado. É completamente geral e se comunica através de um protocolo especificado. O que você pode fazer é personalizar sua parte do protocolo, mas isso não significa que você pode alterar andcompletamente o comportamento . O protocolo é:

Imagine Python interpretando "aeb" (isso não acontece literalmente dessa maneira, mas ajuda a entender). Quando se trata de "e", olha para o objeto que acabou de avaliar (a) e pergunta: você é verdadeiro? ( NÃO : você é True?) Se você é um autor de uma classe, pode personalizar esta resposta. Se a aresposta for "não", and(ignora b completamente, não é avaliada e) diz: aé o meu resultado ( NOT : False é o meu resultado).

Se anão responde, andpergunta: qual é o seu comprimento? (Novamente, você pode personalizar isso como um autor da aclasse). Se aresponder 0, andfaz o mesmo que acima - considera falso ( NÃO Falso), pula be dá acomo resultado.

Se aresponder algo diferente de 0 à segunda pergunta ("qual é o seu tamanho"), ou não responder, ou responder "sim" à primeira ("você é verdadeiro"), andavaliará b e diz: bé o meu resultado. Observe que ele NÃO faz bnenhuma pergunta.

A outra maneira de dizer tudo isso é a and bquase igual b if a else a, exceto que a é avaliada apenas uma vez.

Agora sente-se por alguns minutos com caneta e papel e convença-se de que quando {a, b} é um subconjunto de {True, False}, ele funciona exatamente como você esperaria dos operadores booleanos. Mas espero ter convencido você de que é muito mais geral e, como você verá, muito mais útil dessa maneira.

Juntando esses dois

Agora espero que você entenda o exemplo 1. andnão se importa se mylist1 é um número, lista, lambda ou um objeto de uma classe Argmhbl. Ele se importa apenas com a resposta da mylist1 às perguntas do protocolo. E, claro, mylist1 responde 5 à pergunta sobre comprimento, e retorna mylist2. E é isso. Não tem nada a ver com os elementos mylist1 e mylist2 - eles não aparecem na imagem em nenhum lugar.

Segundo exemplo: &emlist

Por outro lado, &é um operador como outro qualquer, como +por exemplo. Pode ser definido para um tipo, definindo um método especial nessa classe. intdefine-o como bit a bit "e", e bool define-o como lógico "e", mas isso é apenas uma opção: por exemplo, conjuntos e alguns outros objetos, como visualizações de teclas de ditado, definem-no como uma interseção de conjunto. listsimplesmente não define, provavelmente porque Guido não pensou em nenhuma maneira óbvia de defini-lo.

entorpecido

Por outro lado: -D, matrizes numpy são especiais, ou pelo menos estão tentando ser. Obviamente, numpy.array é apenas uma classe, não pode substituir de andforma alguma, e faz a melhor coisa: quando perguntado "você é verdadeiro", numpy.array gera um ValueError, dizendo efetivamente "por favor, refaça a pergunta, meu visão da verdade não se encaixa no seu modelo ". (Observe que a mensagem ValueError não fala and- porque numpy.array não sabe quem está fazendo a pergunta; apenas fala sobre a verdade.)

Pois &, é uma história completamente diferente. numpy.array pode defini-lo como desejar e define &consistentemente com outros operadores: pointwise. Então você finalmente consegue o que quer.

HTH,


23

Os operadores booleanos em curto-circuito ( and, or) não podem ser substituídos porque não há uma maneira satisfatória de fazer isso sem introduzir novos recursos de linguagem ou sacrificar o curto-circuito. Como você pode ou não saber, eles avaliam o primeiro operando como seu valor de verdade e, dependendo desse valor, avaliam e retornam o segundo argumento ou não avaliam o segundo argumento e retornam o primeiro:

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

Observe que o resultado (resultado da avaliação do) operando real é retornado, e não o valor verdadeiro.

A única maneira de personalizar seu comportamento é sobrescrever __nonzero__(renomeado __bool__em Python 3), para que você possa afetar qual operando é retornado, mas não retornar algo diferente. As listas (e outras coleções) são definidas como "verdadeiras" quando contêm alguma coisa e "falsey" quando estão vazias.

As matrizes NumPy rejeitam essa noção: para os casos de uso que visam, são comuns duas noções diferentes de verdade: (1) se algum elemento é verdadeiro e (2) se todos os elementos são verdadeiros. Como esses dois são completamente (e silenciosamente) incompatíveis, e nenhum é claramente mais correto ou mais comum, o NumPy se recusa a adivinhar e exige que você use explicitamente .any()ou .all().

&e |(e not, a propósito) pode ser totalmente anulado, pois não provoca curto-circuito. Eles podem retornar qualquer coisa quando substituídos, e o NumPy faz bom uso disso para realizar operações com elementos, como acontece com praticamente qualquer outra operação escalar. As listas, por outro lado, não transmitem operações entre seus elementos. Assim como mylist1 - mylist2não significa nada e mylist1 + mylist2significa algo completamente diferente, não há &operador para listas.


3
Um exemplo particularmente interessante do que isso pode produzir é [False] or [True]avaliar [False]e [False] and [True]avaliar [True].
Rob Watts

16

Exemplo 1:

É assim que o operador e funciona.

x e y => se x é falso, então x , senão y

Então, em outras palavras, como mylist1não é False, o resultado da expressão é mylist2. (Somente listas vazias são avaliadas como False.)

Exemplo 2:

O &operador é um pouco bit a bit e, como você mencionou. As operações bit a bit funcionam apenas em números. O resultado de um & b é um número composto de 1s em bits, que são um tanto em um e b . Por exemplo:

>>> 3 & 1
1

É mais fácil ver o que está acontecendo usando um literal binário (mesmos números acima):

>>> 0b0011 & 0b0001
0b0001

As operações bit a bit são semelhantes em conceito às operações booleanas (verdade), mas funcionam apenas em bits.

Então, dadas algumas declarações sobre o meu carro

  1. Meu carro é vermelho
  2. Meu carro tem rodas

O "e" lógico dessas duas instruções é:

(meu carro está vermelho?) e (o carro tem rodas?) => verdadeiro lógico com valor falso

Ambos são verdadeiros, pelo menos para o meu carro. Portanto, o valor da declaração como um todo é logicamente verdadeiro.

O bit a bit "e" dessas duas instruções é um pouco mais nebuloso:

(o valor numérico da declaração 'meu carro é vermelho') & (o valor numérico da declaração 'meu carro tem rodas') => número

Se o python souber converter as instruções em valores numéricos, o fará e calculará o bit a bit - e os dois valores. Isso pode levar você a acreditar que &é intercambiável and, mas, como no exemplo acima, são coisas diferentes. Além disso, para os objetos que não podem ser convertidos, você obterá um TypeError.

Exemplos 3 e 4:

O Numpy implementa operações aritméticas para matrizes:

Operações aritméticas e de comparação em ndarrays são definidas como operações entre elementos, e geralmente produzem objetos ndarray como resultados.

Mas não implementa operações lógicas para matrizes, porque você não pode sobrecarregar operadores lógicos em python . É por isso que o exemplo três não funciona, mas o exemplo quatro funciona.

Então, para responder à sua pergunta andvs &: use and.

As operações bit a bit são usadas para examinar a estrutura de um número (quais bits estão definidos, quais bits não estão definidos). Esse tipo de informação é usado principalmente em interfaces de sistema operacional de baixo nível ( bits de permissão unix , por exemplo). A maioria dos programas python não precisa saber disso.

As operações lógicas ( and, or, not), no entanto, são usados o tempo todo.


14
  1. Em Python, uma expressão de X and Yretorno Y, dado que bool(X) == Trueou qualquer um Xou é Yavaliado como Falso, por exemplo:

    True and 20 
    >>> 20
    
    False and 20
    >>> False
    
    20 and []
    >>> []
    
  2. O operador bit a bit simplesmente não está definido para listas. Mas é definido para números inteiros - operando sobre a representação binária dos números. Considere 16 (01000) e 31 (11111):

    16 & 31
    >>> 16
    
  3. NumPy não é um médium, não sabe, se você quer dizer que, por exemplo, [False, False]deve ser igual a Trueuma expressão lógica. Nele, substitui um comportamento padrão do Python, que é: "Qualquer coleção vazia com len(collection) == 0is False".

  4. Provavelmente, um comportamento esperado das matrizes e do operador de NumPy.


Falso e 20 retornos falsa
Rahul

4

Para o primeiro exemplo e base no documento do django,
ele sempre retornará a segunda lista; na verdade, uma lista não vazia é vista como um valor True para Python, portanto, o python retorna o valor 'last' True, de modo que a 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]

4

As operações com uma lista Python operam na lista . list1 and list2verificará se list1está vazio e retornará list1se estiver e list2se não estiver. list1 + list2será anexado list2a list1, para que você obtenha uma nova lista com len(list1) + len(list2)elementos.

Operadores que só fazem sentido quando aplicados em elementos, como &, raise a TypeError, pois operações em elementos não são suportadas sem fazer loop entre os elementos.

As matrizes Numpy suportam operações com elementos . array1 & array2calculará o bit a bit ou para cada elemento correspondente em array1e array2. array1 + array2calculará a soma de cada elemento correspondente em array1e array2.

Isso não funciona para ande or.

array1 and array2 é essencialmente uma abreviação para o seguinte código:

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

Para isso, você precisa de uma boa definição de bool(array1). Para operações globais como as usadas nas listas Python, a definição é que bool(list) == Truese listnão estiver vazio e Falsese estiver vazio. Para operações de elementos do numpy, existe alguma desambiguação para verificar se algum elemento é avaliado Trueou se todos os elementos são avaliados True. Como ambos são discutivelmente corretos, o numpy não adivinha e gera um ValueErrorquando bool()é (indiretamente) chamado em uma matriz.


0

Boa pergunta. Semelhante à observação que você tem sobre os exemplos 1 e 4 (ou devo dizer 1 e 4 :)) sobre operadores lógicos de andbits &, experimentei o sumoperador. O numpy sume o py também sumse comportam de maneira diferente. Por exemplo:

Suponha que "mat" seja uma matriz numpy 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]])

Então numpy.sum (mat) fornece a soma total de toda a matriz. Enquanto a soma interna do Python, como sum (mat), totaliza apenas ao longo do eixo. Ver abaixo:

np.sum(mat)  ## --> gives 325
sum(mat)     ## --> gives array([55, 60, 65, 70, 75])
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.