Вопрос-Ответ

'and' (boolean) vs '&' (bitwise) - Why difference in behavior with lists vs numpy arrays?

'and' (логическое значение) по сравнению с '&' (побитовое) - Почему разница в поведении со списками по сравнению с массивами numpy?

Чем объясняется разница в поведении логических и побитовых операций над списками по сравнению с массивами 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 vs &. Почему списки и массивы NumPy ведут себя по-разному с этими операторами?

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

Переведено автоматически
Ответ 1

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 with &.



Ответ 2

О нас list

Сначала очень важный момент, из которого все вытекает (я надеюсь).

В обычном Python list никоим образом не является чем-то особенным (за исключением наличия симпатичного синтаксиса для построения, что в основном является исторической случайностью). Как только список [3,2,6] создан, он по сути является обычным объектом Python, таким как число 3, набор {3,7} или функция lambda x: x+5.

(Да, он поддерживает изменение своих элементов, и итерацию, и многое другое, но это именно то, что представляет собой тип: он поддерживает одни операции, но не поддерживает некоторые другие. int поддерживает возведение в степень, но это не делает его особенным - это просто то, что представляет собой int. lambda поддерживает вызовы, но это не делает его особенным - в конце концов, для этого и существует lambda :).

О нас and

and не является оператором (вы можете назвать это "operator", но вы также можете вызвать оператор "for" :). Операторы в Python - это (реализованные через) методы, вызываемые для объектов некоторого типа, обычно записываемые как часть этого типа. У метода нет возможности выполнять вычисление некоторых своих операндов, но and может (и должен) это делать.

Следствием этого является то, что and не может быть перегружен, точно так же, как for не может быть перегружен. Он полностью общий и взаимодействует по указанному протоколу. Что вы можете сделать, так это настроить свою часть протокола, но это не значит, что вы можете полностью изменить поведение and. Протокол является:

Представьте, что Python интерпретирует "a и b" (это происходит не буквально, но это помогает пониманию). Когда дело доходит до "и", он смотрит на объект, который он только что оценил (a), и спрашивает его: вы верны? (НЕ: вы True?) Если вы являетесь автором класса a, вы можете настроить этот ответ. Если a отвечает "нет", and (полностью пропускает b, оно вообще не оценивается, и) говорит: a это мой результат (НЕ: False - мой результат).

Если a не отвечает, and спрашивает: какова ваша длина? (Опять же, вы можете настроить это как автор класса a's). Если a отвечает 0, and делает то же самое, что и выше - считает это значение false (НЕ False), пропускает b и выдает a в качестве результата.

Если a на второй вопрос отвечает что-то отличное от 0 ("какова ваша длина"), или вообще не отвечает, или отвечает "да" на первый ("вы правы"), and вычисляет b и говорит: b это мой результат. Обратите внимание, что он НЕ задает b никаких вопросов.

Другой способ сказать все это заключается в том, что a and b это почти то же самое, что и b if a else a, за исключением того, что a вычисляется только один раз.

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

Объединение этих двух

Теперь, я надеюсь, вы поняли свой пример 1. and неважно, является ли mylist1 числом, списком, лямбдой или объектом класса Argmhbl. Его просто волнует ответ mylist1 на вопросы протокола. И, конечно, mylist1 отвечает 5 на вопрос о длине, поэтому и возвращает mylist2 . И это все. Это не имеет ничего общего с элементами mylist1 и mylist2 - они нигде не отображаются.

Второй пример: & на list

С другой стороны, & это оператор, подобный любому другому, например, как +. Его можно определить для типа, определив специальный метод в этом классе. int определяет это как побитовое "и", а bool определяет это как логическое "и", но это только один вариант: например, наборы и некоторые другие объекты, такие как представления dict keys , определяют это как пересечение множеств. list просто не определяет это, вероятно, потому, что Guido не придумал какого-либо очевидного способа определения этого.

numpy

С другой стороны: -D, массивы numpy являются особенными или, по крайней мере, пытаются ими быть. Конечно, numpy.array - это всего лишь класс, он никоим образом не может переопределить and, поэтому он делает следующую лучшую вещь: когда его спрашивают "вы правы", numpy.array выдает ValueError , фактически говоря "пожалуйста, перефразируйте вопрос, мое представление об истине не вписывается в вашу модель". (Обратите внимание, что сообщение ValueError не говорит о and - потому что numpy.array не знает, кто задает ему вопрос; оно просто говорит об истине.)

Для & это совершенно другая история. numpy.array может определять его по своему усмотрению, и он определяет & последовательно с другими операторами: точечно. Итак, вы наконец получили то, что хотели.

HTH,

Ответ 3

Логические операторы короткого замыкания (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().

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

Ответ 4

Пример 1:

Вот как работает оператор and.

x и y => если x равно false, то x, иначе y

Другими словами, поскольку mylist1 нет False, результатом выражения является mylist2. (Только пустые списки вычисляются до False.)

Пример 2:

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

>>> 3 & 1
1

Проще увидеть, что происходит, используя двоичный литерал (те же числа, что и выше):

>>> 0b0011 & 0b0001
0b0001

Побитовые операции похожи по концепции на логические (истинностные) операции, но они работают только с битами.

Итак, учитывая пару утверждений о моей машине


  1. Моя машина красная

  2. У моей машины есть колеса

Логическое "и" этих двух утверждений:


(моя машина красная?) и (у машины есть колеса?) => логическое значение true вместо false


Оба из которых верны, по крайней мере, для моей машины. Таким образом, значение оператора в целом логически верно.

Побитовое "and" этих двух операторов немного более туманно:


(числовое значение утверждения "моя машина красная") & (числовое значение утверждения "у моей машины есть колеса") => число


Если python знает, как преобразовать операторы в числовые значения, то он сделает это и вычислит побитовое значение-and из двух значений. Это может навести вас на мысль, что & взаимозаменяемо с and, но, как и в приведенном выше примере, это разные вещи. Кроме того, для объектов, которые нельзя преобразовать, вы просто получите TypeError.

Пример 3 и 4:

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


Арифметические операции и операции сравнения в ndarrays определяются как поэлементные операции и обычно дают объекты ndarray в качестве результатов.


Но не реализует логические операции над массивами, потому что вы не можете перегружать логические операторы в python. Вот почему пример три не работает, но пример четыре работает.

Итак, чтобы ответить на ваш вопрос and vs &: Используйте and.

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

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

python numpy