'і' (булева) проти 'і' (побітові) - Чому різниця в поведінці зі списками проти нумерованих масивів?


142

Чим пояснюється різниця в поведінці булевих та бітових операцій у списках проти NumPy-масивів?

Мене плутає правильне використання &vs andу Python, проілюстроване в наступних прикладах.

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!

Ця відповідь і ця відповідь допомогли мені зрозуміти, що andце булева операція, але &це бітова операція.

Я читав про побізні операції, щоб краще зрозуміти концепцію, але я намагаюся використовувати цю інформацію, щоб зрозуміти мої вище 4 приклади.

Приклад 4 привів мене до бажаного виходу, так що це добре, але я до сих пір плутають про те, коли / як / чому я повинен використовувати andпроти &. Чому списки та масиви NumPy поводяться по-різному з цими операторами?

Чи може хтось допомогти мені зрозуміти різницю між булевими та розрядними операціями, щоб пояснити, чому вони по-різному обробляють списки та масиви NumPy?


2
У Numpy є np.bitwise_and()і np.logical_and()та друзі, щоб уникнути плутанини.
Дітріх

1
У прикладі 1 mylist1 and mylist2не виходить такий самий результат, як mylist2 and mylist1, оскільки повертається другий список, як вказав делнан.
користувач2015487

Відповіді:


113

andперевіряє, чи обидва вирази логічно Trueпід час &(при використанні з True/ Falseзначень) тестів, якщо обидва є True.

У Python порожні вбудовані об'єкти зазвичай трактуються як логічно, Falseтоді як непусті вбудовані - логічно True. Це полегшує випадок загального використання, коли ви хочете щось зробити, якщо список порожній, а щось інше, якщо список не є. Зауважте, що це означає, що список [False] є логічним True:

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

Отже, у Прикладі 1 перший список не порожній і, отже, логічно True, тому значення істинності значень так andсамо, як і у другого списку. (У нашому випадку другий список не порожній і, отже, логічно True, але ідентифікаційний, що вимагає зайвого кроку обчислення.)

Наприклад, 2, списки не можуть змістовно поєднуватись побіжно, оскільки вони можуть містити довільні на відміну елементи. До речей, які можна поєднати побіжно, належать: Точки і помилки, цілі числа.

Об'єкти NumPy, навпаки, підтримують векторизовані обчислення. Тобто вони дозволяють виконувати одні й ті самі операції над кількома фрагментами даних.

Приклад 3 не вдається, оскільки масиви NumPy (довжини> 1) не мають значення істини, оскільки це запобігає логічній плутанині на основі вектора.

Приклад 4 - це просто векторизована бітова andоперація.

Нижня лінія

  • Якщо ви не маєте справу з масивами і не виконуєте математичних маніпуляцій цілих чисел, ви, мабуть, хочете and.

  • Якщо у вас є вектори значень істинності , які ви хочете об'єднати, використання numpyз &.


26

Про list

Спочатку дуже важливий момент, з якого все буде випливати (сподіваюся).

У звичайному Python listне є особливим у жодному разі (крім того, що милий синтаксис для побудови, що здебільшого є історичною випадковістю). Після того, як список [3,2,6]складається, він для всіх намірів і цілей є просто звичайним об'єктом Python, таким як число 3, набір {3,7}або функція lambda x: x+5.

(Так, він підтримує зміну своїх елементів, він підтримує ітерацію та багато інших речей, але це лише такий тип: він підтримує деякі операції, а не підтримує деякі інші. Int підтримує підвищення до потужності, але це не робить зробити його дуже особливим - це просто те, що таке int. лямбда підтримує дзвінки, але це не робить його дуже особливим - саме для цього і є лямбда :).

Про and

andне є оператором (ви можете назвати його "оператором", але ви можете також зателефонувати "для" оператора :). Оператори в Python - це (реалізовані через) методи, викликані на об'єктах певного типу, зазвичай записаних як частина цього типу. Немає способу проводити оцінку деяких своїх операндів, але це andможе (і повинно) зробити це.

Наслідком цього є те, що andне можна перевантажувати, як forне можна перевантажувати. Він повністю загальний і спілкується через визначений протокол. Що ви можете зробити, це налаштувати свою частину протоколу, але це не означає, що ви можете повністю змінити поведінку and. Протокол:

Уявіть, що Python інтерпретує "a і b" (це відбувається не буквально так, але це допомагає зрозуміти). Коли йдеться про "і", він дивиться на об'єкт, який він щойно оцінив (а), і запитує його: ти правдивий? ( НЕ : ти True?) Якщо ви автор класу, ви можете налаштувати цю відповідь. Якщо aвідповіді "ні", and(пропускає b повністю, це зовсім не оцінюється, і) каже: aце мій результат ( НЕ : Неправдивий - це мій результат).

Якщо aне відповідає, andзапитайте: яка ваша довжина? (Знову ж, ви можете налаштувати це як автор aкласу). Якщо aвідповідь 0, andробить те саме, що вище - вважає помилковим ( НЕ помилковим), пропускає b і дає aрезультат.

Якщо aна друге запитання відповідає щось інше, ніж 0 ("яка ваша довжина"), або вона взагалі не відповідає, або відповідає "так" на перше ("ви правдиві"), andоцінює b і каже: bце мій результат. Зауважте, що він НЕ задає bжодних питань.

Інший спосіб сказати все це - a and bце майже те саме, що b if a else a, за винятком а, оцінюється лише один раз.

Тепер посидьте кілька хвилин ручкою та папером і переконайте себе, що коли {a, b} є підмножиною {True, False}, вона працює точно так, як ви очікували від булевих операторів. Але я сподіваюся, що я переконав вас, що це набагато загальніше, і як ви побачите, цей спосіб набагато корисніший.

Поєднання цих двох разом

Тепер я сподіваюся, що ви зрозуміли ваш приклад 1. andБайдуже, чи mylist1 - це число, список, лямбда або об’єкт класу Argmhbl. Це просто піклується про відповідь mylist1 на запитання протоколу. І звичайно, mylist1 відповідає 5 на запитання про довжину, тому повертає mylist2. І це все. Це не має нічого спільного з елементами mylist1 та mylist2 - вони ніде не вводять зображення.

Другий приклад: &наlist

З іншого боку, &це оператор, як і будь-який інший, як +наприклад. Це можна визначити для типу, визначивши спеціальний метод для цього класу. intвизначає його як бітові "та", а bool визначає це як логічні "та", але це лише один варіант: наприклад, набори та деякі інші об'єкти, такі як погляди клавіш dict, визначають його як перетин набору. listпросто не визначає це, ймовірно, тому, що Гвідо не думав про якийсь очевидний спосіб його визначення.

онімілий

На іншій нозі: -D, Numpy масиви є спеціальними, або , по крайней мере , вони намагаються бути. Звичайно, numpy.array - це просто клас, він не може переоцінити andжодним чином, тому він робить наступне найкраще: коли запитують "ти правдивий", numpy.array піднімає ValueError, ефективно кажучи "будь ласка, перефразуйте питання, мій погляд на правду не вписується у вашу модель ". (Зверніть увагу, що повідомлення ValueError не говорить про те, andтому що numpy.array не знає, хто йому задає це питання; воно просто говорить про правду.)

Бо &це зовсім інша історія. numpy.array може визначати його за своїм бажанням, і він визначає &послідовно з іншими операторами: в точку. Так ви нарешті отримуєте те, що хочете.

HTH,


23

Булеві оператори з коротким замиканням ( and, or) не можуть бути відмінені, тому що немає вдосконального способу зробити це без введення нових мовних особливостей або принесення в жертву короткого замикання. Як ви можете, чи не можете знати, вони оцінюють перший операнд за його значенням істинності, і залежно від цього значення або оцінюють, і повертають другий аргумент, або не оцінюють другий аргумент і повертають перший:

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

Зауважте, що (результат оцінки) повертається фактичний операнд, а не його значення істини.

Єдиний спосіб налаштувати їх поведінку - це переозначення __nonzero__(перейменований на __bool__Python 3), так що ви можете впливати на те, який операнд повертається, але не повертати щось інше. Списки (та інші колекції) визначаються як "тризубні", коли вони взагалі щось містять, і "фальсифіковані", коли вони порожні.

Масиви NumPy відкидають це поняття: для випадків використання, на які вони спрямовані, загальні два поняття істини: (1) чи правда якийсь елемент і (2) чи всі елементи правдиві. Оскільки ці двоє повністю (і мовчки) несумісні, і жодне з них не є явно правильнішим або більш поширеним, NumPy відмовляється здогадуватися і вимагає від вас явного використання .any()або .all().

&і |not, до речі, може бути повністю перекрито, оскільки вони не мають короткого замикання. Вони можуть повернути що завгодно будь-що, якщо їх перекриють, і NumPy добре використовує це для виконання елементів, як це робиться практично при будь-якій іншій скалярній операції. Списки, з іншого боку, не транслюють операції через їх елементи. Так само, як mylist1 - mylist2нічого не означає і mylist1 + mylist2означає щось зовсім інше, немає &оператора для списків.


3
Один особливо цікавий приклад того, що це може дати, - це [False] or [True]оцінка [False]та [False] and [True]оцінка [True].
Роб Уоттс

16

Приклад 1:

Ось як працює і оператор.

x і y => якщо x хибне, то x , інакше y

Так іншими словами, оскільки mylist1це не так False, результат вираження є mylist2. (Лише порожні списки оцінюють False.)

Приклад 2:

&Оператор для побітового і, як ви говорите. Побітні операції працюють лише на числах. Результат а & b - це число, що складається з 1s у бітах, що дорівнює 1 і a, і b . Наприклад:

>>> 3 & 1
1

Простіше зрозуміти, що відбувається за допомогою двійкового літералу (ті ж числа, що і вище):

>>> 0b0011 & 0b0001
0b0001

Побітові операції за принципом схожі на булеві (істинні) операції, але вони працюють лише на бітах.

Отже, давши пару заяв про мій автомобіль

  1. Моя машина червона
  2. У моїй машині є колеса

Логічним "і" цих двох тверджень є:

(чи моя машина червона?) та (чи автомобіль має колеса?) => логічне значення правдивого значення

І те і інше, як мінімум, для мого автомобіля. Тож значення твердження в цілому логічно вірно.

Побіт "і" цих двох тверджень трохи більш туманний:

(числове значення висловлювання "мій автомобіль червоний") & (числове значення твердження "мій автомобіль має колеса") => число

Якщо python знає, як перетворити оператори в числові значення, тоді це зробить і обчислить порозрядне - і два значення. Це може призвести до того, що ви вважаєте, що &це взаємозамінне and, але як і у наведеному вище прикладі, це різні речі. Також для об’єктів, які неможливо перетворити, ви просто отримаєте TypeError.

Приклад 3 і 4:

Numpy реалізує арифметичні операції для масивів:

Арифметичні та порівняльні операції на ndarrays визначаються як елементні операції і, як правило, дають об'єкти ndarray як результати.

Але не реалізує логічні операції для масивів, тому що ви не можете перевантажувати логічні оператори в python . Ось чому приклад три не працює, а приклад чотири - це.

Отже, щоб відповісти на ваше запитання andvs &: Використовуйте and.

Побітові операції використовуються для вивчення структури числа (які біти встановлені, які біти не встановлені). Така інформація використовується здебільшого в інтерфейсах операційної системи низького рівня (наприклад, біти дозволів Unix ). Більшість програм python цього не потрібно знати.

Логічні операції ( and, or, not), проте, використовуються весь час.


14
  1. У Python вираження X and Yповернень з Yурахуванням того, що bool(X) == Trueабо будь-яке Xабо Yоцінюється як False, наприклад:

    True and 20 
    >>> 20
    
    False and 20
    >>> False
    
    20 and []
    >>> []
    
  2. Бітовий оператор просто не визначений для списків. Але це визначено для цілих чисел - оперування над двійковим поданням чисел. Розглянемо 16 (01000) та 31 (11111):

    16 & 31
    >>> 16
    
  3. NumPy - це не екстрасенс, він не знає, чи ти маєш на увазі, що наприклад, у логічному виразі [False, False]має бути рівний True. У цьому це перекриває стандартну поведінку Python, яка є: "Будь-яка порожня колекція з len(collection) == 0є False".

  4. Можливо, очікувана поведінка масивів & оператора NumPy.


Хибні і 20 повертає Неправдиві
Рахул

4

Для першого прикладу та бази на документі django
Він завжди повертає другий список, бо не порожній список розглядається як значення True для Python, таким чином python повертає "останнє" значення True, тому другий список

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

Операції зі списком Python працюють за цим списком . list1 and list2перевірить, чи list1порожній він, і повернеться, list1якщо він є, а list2якщо ні. list1 + list2буде додано list2до list1, тож ви отримаєте новий список з len(list1) + len(list2)елементами.

Оператори, які мають сенс лише тоді, коли застосовуються елементи, такі як, наприклад &, піднімають a TypeError, оскільки операції з елементами не підтримуються без прокручування елементів.

Numpy масиви підтримують поелементно операції. array1 & array2буде обчислюватися порозрядно або для кожного відповідного елемента в array1і array2. array1 + array2обчислить суму для кожного відповідного елемента в array1і array2.

Це не працює andі для or.

array1 and array2 по суті є короткою рукою для наступного коду:

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

Для цього вам потрібно добре визначити bool(array1). Для глобальних операцій, таких як список, що використовується в списках Python, визначення полягає в тому, що bool(list) == Trueякщо listвін не порожній і Falseякщо він порожній. Для операцій з елементами numpy існує певна розбіжність у тому, чи потрібно перевіряти, чи будь-який елемент оцінює True, чи всі елементи оцінюють True. Оскільки і те, імовірно, правильне, numpy не здогадується і піднімає a, ValueErrorколи bool()(побічно) викликається масив.


0

Гарне питання. Подібно до спостереження, яке ви маєте щодо прикладів 1 і 4 (або я повинен сказати 1 і 4 :)) щодо логічних операцій, andрозрядних &я, я відчував у sumоператора. Нумі sumі пі sumповодяться також по-різному. Наприклад:

Припустимо, що "килимок" - це масивний 5x5 2d масив, такий як:

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

Тоді numpy.sum (mat) дає загальну суму всієї матриці. Тоді як вбудована сума з Python, така як сума (мат), підсумовується лише вздовж осі. Дивись нижче:

np.sum(mat)  ## --> gives 325
sum(mat)     ## --> gives array([55, 60, 65, 70, 75])
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.