„and” (boolean) vs „&” (bitwise) - Dlaczego różnica w zachowaniu z listami a tablicami numpy?


142

Co wyjaśnia różnicę w zachowaniu operacji logicznych i bitowych na listach w porównaniu z tablicami NumPy?

Jestem zdezorientowany co do odpowiedniego użycia &vs andw Pythonie, co ilustrują poniższe przykłady.

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!

Ta odpowiedź i ta odpowiedź pomogły mi zrozumieć, że andjest to operacja logiczna, ale &operacja bitowa.

Czytałem o operacjach bitowych, aby lepiej zrozumieć koncepcję, ale staram się wykorzystać te informacje, aby nadać sens moim powyższym 4 przykładom.

Przykład 4 doprowadziły mnie do żądanego wyjścia, tak że jest w porządku, ale jestem nadal mylić o tym, kiedy / jak / dlaczego należy używać andvs &. Dlaczego listy i tablice NumPy zachowują się inaczej z tymi operatorami?

Czy ktoś może mi pomóc zrozumieć różnicę między operacjami logicznymi i bitowymi, aby wyjaśnić, dlaczego inaczej obsługują listy i tablice NumPy?


2
W Numpy jest np.bitwise_and()i np.logical_and()i przyjaciele, aby uniknąć nieporozumień.
Dietrich

1
W przykładzie 1 mylist1 and mylist2nie mylist2 and mylist1zwraca tego samego wyniku, co , ponieważ zwracana jest druga lista wskazana przez delnan.
user2015487

Odpowiedzi:


113

andsprawdza, czy oba wyrażenia są logiczne, Truepodczas &gdy (gdy są używane z True/ Falsevalues) testuje, jeśli oba są True.

W Pythonie puste obiekty wbudowane są zwykle traktowane jako logiczne, Falsepodczas gdy niepuste obiekty wbudowane są logiczne True. Ułatwia to typowy przypadek użycia, w którym chcesz coś zrobić, jeśli lista jest pusta, a coś innego, jeśli lista nie jest. Zauważ, że oznacza to, że lista [False] jest logiczna True:

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

Tak więc w przykładzie 1 pierwsza lista nie jest pusta, a zatem logicznie True, więc prawdziwa wartość andjest taka sama, jak druga lista. (W naszym przypadku druga lista nie jest pusta i dlatego jest logiczna True, ale zidentyfikowanie tego wymagałoby niepotrzebnego kroku obliczeniowego).

Na przykład 2, list nie można łączyć w sensowny sposób w sposób bitowy, ponieważ mogą zawierać dowolne niepodobne do siebie elementy. Rzeczy, które można łączyć bitowo, to: prawda i fałsz, liczby całkowite.

Z kolei obiekty NumPy obsługują obliczenia wektoryzowane. Oznacza to, że umożliwiają wykonywanie tych samych operacji na wielu fragmentach danych.

Przykład 3 zawodzi, ponieważ tablice NumPy (o długości> 1) nie mają wartości prawdziwej, ponieważ zapobiega to pomyłkom w logice wektorowej.

Przykład 4 to po prostu wektoryzowana andoperacja bitowa .

Podsumowanie

  • Jeśli nie masz do czynienia z tablicami i nie wykonujesz operacji matematycznych na liczbach całkowitych, prawdopodobnie chcesz and.

  • Jeśli masz wektory wartości prawdy, które chcesz połączyć, użyj numpyz &.


26

O list

Najpierw bardzo ważny punkt, od którego wszystko się potoczy (mam nadzieję).

W zwykłym Pythonie listnie jest niczym szczególnym (z wyjątkiem posiadania ładnej składni do konstruowania, co jest głównie historycznym przypadkiem). Utworzona lista [3,2,6]jest pod każdym względem zwykłym obiektem Pythona, takim jak liczba 3, zestaw {3,7}lub funkcja lambda x: x+5.

(Tak, obsługuje zmianę swoich elementów, obsługuje iterację i wiele innych rzeczy, ale tym właśnie jest typ: obsługuje niektóre operacje, podczas gdy inne nie obsługuje. Int obsługuje podnoszenie do potęgi, ale to nie jest uczyń go wyjątkowym - po prostu tym jest int. lambda obsługuje wywoływanie, ale to nie czyni go wyjątkowym - w końcu do tego służy lambda :).

O and

andnie jest operatorem (możesz go nazwać „operatorem”, ale możesz też zadzwonić do operatora „for” :). Operatory w Pythonie to (zaimplementowane) metody wywoływane na obiektach pewnego typu, zwykle napisane jako część tego typu. Nie ma sposobu, aby metoda zawierała ocenę niektórych swoich operandów, ale andmoże (i musi) to zrobić.

Konsekwencją tego jest to, że andnie można go przeciążać, tak jak fornie można przeciążać. Jest to całkowicie ogólne i komunikuje się za pomocą określonego protokołu. Co można zrobić, to dostosować część protokołu, ale to nie znaczy, można zmienić zachowanie andcałkowicie. Protokół jest następujący:

Wyobraź sobie, że Python interpretuje „a i b” (nie dzieje się to dosłownie w ten sposób, ale pomaga to zrozumieć). Jeśli chodzi o „i”, patrzy na obiekt, który właśnie ocenił (a) i pyta: czy to prawda? ( NIE : jesteś True?) Jeśli jesteś autorem klasy a, możesz dostosować tę odpowiedź. Jeśli aodpowie „nie” and(całkowicie pomija b, nie jest ono w ogóle oceniane i) mówi: ajest moim wynikiem ( NIE : Fałsz to mój wynik).

Jeśli anie odpowiada, andpyta: jaka jest twoja długość? (Ponownie, możesz dostosować to jako autor aklasy). Jeśli aodpowiada 0, androbi to samo, co powyżej - uważa to za fałszywe ( NIE fałszywe), pomija b i podaje awynik.

Jeśli aodpowie coś innego niż 0 na drugie pytanie („jaka jest twoja długość”), lub w ogóle nie odpowie lub odpowie „tak” na pierwsze pytanie („czy jesteś prawdziwy”), andocenia b i mówi: bto mój wynik. Zauważ, że NIE zadaje bżadnych pytań.

Innym sposobem na powiedzenie tego wszystkiego jest to, że a and bjest prawie tak samo, jak b if a else a, z wyjątkiem tego, że a jest oceniane tylko raz.

Teraz usiądź na kilka minut z długopisem i kartką i przekonaj się, że kiedy {a, b} jest podzbiorem {Prawda, Fałsz}, to działa dokładnie tak, jak można by oczekiwać od operatorów boolowskich. Mam jednak nadzieję, że przekonałem Cię, że jest on znacznie bardziej ogólny i, jak zobaczysz, w ten sposób znacznie bardziej przydatny.

Łącząc te dwa razem

Mam nadzieję, że rozumiesz swój przykład 1. andNie obchodzi mnie, czy mylist1 jest liczbą, listą, lambdą czy obiektem klasy Argmhbl. Dba tylko o odpowiedź mylist1 na pytania protokołu. I oczywiście mylist1 odpowiada 5 na pytanie o długość, więc i zwraca mylist2. I to wszystko. Nie ma to nic wspólnego z elementami mylist1 i mylist2 - nigdzie nie pojawiają się na obrazku.

Drugi przykład: &onlist

Z drugiej strony &jest operatorem jak każdy inny, jak +np. Można go zdefiniować dla typu, definiując specjalną metodę dla tej klasy. intdefiniuje to jako bitowe „i”, a bool definiuje jako logiczne „i”, ale to tylko jedna opcja: na przykład zbiory i niektóre inne obiekty, takie jak widoki kluczy dykt, definiują to jako zestaw przecięcia. listpo prostu tego nie definiuje, prawdopodobnie dlatego, że Guido nie wymyślił żadnego oczywistego sposobu zdefiniowania tego.

tępy

Z drugiej strony: -D, tablice numpy wyjątkowe, a przynajmniej starają się być. Oczywiście numpy.array jest tylko klasą, nie może andw żaden sposób nadpisać , więc robi następną najlepszą rzecz: gdy zapytamy „czy jesteś prawdziwy”, numpy.array wywołuje błąd ValueError, skutecznie mówiąc „proszę przeformułować pytanie, mój pogląd na prawdę nie pasuje do twojego modelu ”. (Zwróć uwagę, że wiadomość ValueError nie mówi o tym and- ponieważ numpy.array nie wie, kto zadaje pytanie; po prostu mówi o prawdzie).

Bo &to zupełnie inna historia. numpy.array może zdefiniować go tak, jak chce, i definiuje &spójnie z innymi operatorami: punktowy. Więc w końcu dostajesz to, czego chcesz.

HTH,


23

Skracające się operatory boolowskie ( and, or) nie mogą być nadpisane, ponieważ nie ma satysfakcjonującego sposobu na zrobienie tego bez wprowadzenia nowych funkcji języka lub poświęcenia zwarcia. Jak możesz wiedzieć lub nie, oceniają one pierwszy argument pod kątem jego prawdziwości iw zależności od tej wartości albo oceniają i zwracają drugi argument, albo nie oceniają drugiego argumentu i zwracają pierwszy:

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

Zauważ, że zwracany jest (wynik oceny) rzeczywisty operand, a nie jego wartość prawdziwa.

Jedynym sposobem dostosowania ich zachowania jest przesłonięcie __nonzero__(zmiana nazwy na __bool__w Pythonie 3), dzięki czemu możesz wpływać na to, który operand zostanie zwrócony, ale nie zwracać czegoś innego. Listy (i inne kolekcje) definiuje się jako „prawdziwe”, gdy w ogóle cokolwiek zawierają, i „falsey”, gdy są puste.

Tablice NumPy odrzucają to pojęcie: w przypadkach użycia, do których dążą, wspólne są dwa różne pojęcia prawdy: (1) czy którykolwiek element jest prawdziwy i (2) czy wszystkie elementy są prawdziwe. Ponieważ te dwa są całkowicie (i cicho) niekompatybilne, i żaden z nich nie jest wyraźnie bardziej poprawny ani bardziej powszechny, NumPy odmawia zgadywania i wymaga wyraźnego użycia .any()lub .all().

&i |(i notprzy okazji) mogą być całkowicie nadpisane, ponieważ nie powodują zwarcia. Mogą w ogóle zwrócić wszystko, gdy są zastępowane, a NumPy dobrze to wykorzystuje do wykonywania operacji elementarnych, tak jak robią to z praktycznie każdą inną operacją skalarną. Z drugiej strony listy nie rozgłaszają operacji na swoich elementach. Tak jak mylist1 - mylist2nic nie znaczy, a mylist1 + mylist2oznacza coś zupełnie innego, nie ma &operatora dla list.


3
Szczególnie interesującym przykładem tego, co to może dać, jest [False] or [True]wartościowanie [False]i [False] and [True]wartościowanie [True].
Rob Watts,

16

Przykład 1:

Tak działa operator and .

x i y => jeśli x jest fałszem, to x , w przeciwnym razie y

Innymi słowy, ponieważ mylist1nie jest False, wynik wyrażenia to mylist2. (Tylko puste listy mają wartość False.)

Przykład 2:

&Operator jest dla bitowego i, jak wspomina. Operacje bitowe działają tylko na liczbach. Wynikiem a i b jest liczba złożona z jedynek w bitach, które są równe 1 w obu a i b . Na przykład:

>>> 3 & 1
1

Łatwiej jest zobaczyć, co się dzieje, używając literału binarnego (te same liczby co powyżej):

>>> 0b0011 & 0b0001
0b0001

Operacje bitowe są koncepcyjnie podobne do operacji logicznych (prawdziwych), ale działają tylko na bitach.

Tak więc, biorąc pod uwagę kilka wypowiedzi na temat mojego samochodu

  1. Mój samochód jest czerwony
  2. Mój samochód ma koła

Logiczne „i” tych dwóch instrukcji to:

(czy mój samochód jest czerwony?) i (czy samochód ma koła?) => logiczna prawda lub fałsz

Oba są prawdziwe, przynajmniej w przypadku mojego samochodu. Zatem wartość całego stwierdzenia jest logicznie prawdziwa.

Bitowe „i” tych dwóch stwierdzeń jest trochę bardziej mgliste:

(wartość liczbowa stwierdzenia „mój samochód jest czerwony”) i (wartość liczbowa stwierdzenia „mój samochód ma koła”) => liczba

Jeśli Python wie, jak przekonwertować instrukcje na wartości liczbowe, zrobi to i obliczy bitowe i dwóch wartości. Może to prowadzić do przekonania, że &jest to wymienne z and, ale tak jak w powyższym przykładzie są to różne rzeczy. Ponadto w przypadku obiektów, których nie można przekonwertować, otrzymasz po prostu plik TypeError.

Przykład 3 i 4:

Numpy implementuje operacje arytmetyczne dla tablic:

Operacje arytmetyczne i porównania na ndarray są definiowane jako operacje elementarne i generalnie dają wyniki ndarray obiektów.

Ale nie implementuje operacji logicznych dla tablic, ponieważ nie można przeciążać operatorów logicznych w Pythonie . Dlatego przykład trzeci nie działa, ale przykład czwarty działa.

Tak, aby odpowiedzieć na Twoje andvs &pytanie: Użyj and.

Operacje bitowe służą do badania struktury liczby (które bity są ustawione, a które nie). Tego rodzaju informacje są najczęściej używane w niskopoziomowych interfejsach systemu operacyjnego ( na przykład bity uprawnień unix ). Większość programów w Pythonie nie musi o tym wiedzieć.

Operacje logiczne ( and, or, not), jednak są wykorzystywane przez cały czas.


14
  1. W Pythonie wyrażenie X and Yzwraca Y, przyjmując, że bool(X) == Truelub którykolwiek z Xlub Yoceniany na False, np .:

    True and 20 
    >>> 20
    
    False and 20
    >>> False
    
    20 and []
    >>> []
    
  2. Operator bitowy po prostu nie jest zdefiniowany dla list. Ale jest zdefiniowany dla liczb całkowitych - operując na binarnej reprezentacji liczb. Rozważmy 16 (01000) i 31 (11111):

    16 & 31
    >>> 16
    
  3. NumPy nie jest medium, nie wie, czy masz na myśli, że np. [False, False]Powinno być równe Truew logicznym wyrażeniu. W tym przypadku zastępuje standardowe zachowanie Pythona, które brzmi: „Dowolna pusta kolekcja zawierająca len(collection) == 0to False”.

  4. Prawdopodobnie oczekiwane zachowanie operatora & tablic NumPy.


False i 20 zwraca False
Rahul

4

Dla pierwszego przykładu i bazuje na dokumencie django
Zawsze zwróci drugą listę, w rzeczywistości niepusta lista jest postrzegana jako wartość True dla Pythona więc python zwraca 'ostatnią' wartość True, więc druga 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

Operacje na liście Pythona działają na liście . list1 and list2sprawdzi, czy list1jest pusta, i zwróci, list1jeśli jest, a list2jeśli nie. list1 + list2dołączy list2do list1, więc otrzymasz nową listę z len(list1) + len(list2)elementami.

Operatory, które mają sens tylko wtedy, gdy są stosowane w odniesieniu do elementów, takie jak &podnoszenie a TypeError, ponieważ operacje na elementach nie są obsługiwane bez zapętlania elementów.

Tablice Numpy obsługują operacje elementarne . array1 & array2obliczy bitowo lub dla każdego odpowiedniego elementu w array1i array2. array1 + array2obliczy sumę dla każdego odpowiedniego elementu w array1i array2.

To nie działa w przypadku andi or.

array1 and array2 jest w zasadzie skrótem dla następującego kodu:

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

W tym celu potrzebujesz dobrej definicji bool(array1). W przypadku operacji globalnych, takich jak używane na listach Pythona, definicja jest bool(list) == Truetaka, że jeśli listnie jest puste, a Falsejeśli jest puste. W przypadku operacji numpy na elementach istnieje pewna niejednoznaczność, czy należy sprawdzić, czy jakikolwiek element ma wartość True, czy wszystkie elementy mają wartość True. Ponieważ oba są prawdopodobnie poprawne, numpy nie zgaduje i zgłasza ValueErrorkiedy bool()jest (pośrednio) wywoływane na tablicy.


0

Dobre pytanie. Podobnie jak w przypadku obserwacji, które masz na temat przykładów 1 i 4 (lub powinienem powiedzieć 1 i 4 :)) z logicznymi operatorami andbitowymi &, doświadczyłem na sumoperatorze. Numpy sumi py również sumzachowują się inaczej. Na przykład:

Załóżmy, że „mat” to tablica numpy 5x5 2D, na przykład:

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]])

Następnie numpy.sum (mat) daje całkowitą sumę całej macierzy. Natomiast suma wbudowana z Pythona, taka jak sum (mat), sumuje się tylko wzdłuż osi. Zobacz poniżej:

np.sum(mat)  ## --> gives 325
sum(mat)     ## --> gives array([55, 60, 65, 70, 75])
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.