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

149

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

Rysqui
источник
2
В Numpy есть np.bitwise_and()и np.logical_and()и друзей , чтобы избежать путаницы.
Дитрих
1
В примере 1 mylist1 and mylist2не выводится тот же результат mylist2 and mylist1, что и, поскольку возвращается второй список, указанный delnan.
user2015487
1
Если кто-то хочет достичь результатов, которые вы ожидали в примере 1, они могут использовать[a and b for a, b in zip(mylist1, mylist2)]
Джо

Ответы:

120

andпроверяет, являются ли оба выражения логическими, в Trueто время как &(при использовании с True/ Falsevalues) проверяет, являются ли оба выражения True.

В Python пустые встроенные объекты обычно обрабатываются логически, Falseа непустые встроенные объекты - логически True. Это упрощает общий случай использования, когда вы хотите что-то сделать, если список пуст, и что-то еще, если список нет. Обратите внимание, что это означает, что список [False] логически True:

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

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

Например, 2, списки не могут быть осмысленно объединены побитовым способом, потому что они могут содержать произвольные непохожие элементы. Вещи, которые можно комбинировать поразрядно, включают: Истины и Ложь, целые числа.

Объекты NumPy, напротив, поддерживают векторизованные вычисления. То есть они позволяют выполнять одни и те же операции с несколькими фрагментами данных.

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

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

Нижняя линия

  • Если вы не имеете дело с массивами и не выполняете математические операции с целыми числами, вы, вероятно, захотите and.

  • Если у вас есть векторы значений истинности, которые вы хотите объединить, используйте numpyс ними &.

Ramcdougal
источник
28

Около 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 оценивается только один раз.

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

Собираем эти двое вместе

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

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

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

тупой

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

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

HTH,

Veky
источник
Это помогло мне лучше понять это, спасибо! мелкий шрифт - я считаю, что когда вы написали «когда {a, b} является подмножеством {True, False}», вы имели в виду «когда a и b находятся в {True, False}».
Джо
1
Это именно то, что означает подмножество. :-) X является подмножеством Y означает, что каждый элемент X также является элементом Y.
Veky
Интересно. Я не думал об этом таким образом. Я думал о {a, b} как о упорядоченной паре (даже несмотря на то, что вы использовали скобки), потому что я думал, что вы хотели попробовать: (True, True), (True, False), (False, True), и (False, False), поэтому я подумал, что (True, True) не является подмножеством {True, False}, но теперь я вижу, что {True, True} является подмножеством {True, False}, поскольку {True , True} = {True}.
Джо
Я не только использовал скобки, но и сказал sub_set_. :-) Если вы не используете такие странные вещи, как определение Куратовски, вы не можете быть подмножеством упорядоченной пары. : -] (TypeError: '<=' не поддерживается между экземплярами 'set' и 'tuple', сказал бы Python .;)
Veky
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].
Роб Уоттс
17

Пример 1:

Так работает оператор и .

x и y => если x ложно, то x , иначе y

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

Пример 2:

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

>>> 3 & 1
1

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

>>> 0b0011 & 0b0001
0b0001

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

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

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

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

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

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

Побитовое «и» этих двух операторов немного более расплывчато:

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

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

Пример 3 и 4:

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

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

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

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

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

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

Сет
источник
15
  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) == 0is 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]
MoiTux
источник
4

Операции со списком Python работают на списке . list1 and list2проверит, list1является ли он пустым, и вернет, list1если это так, а list2если нет. list1 + list2будет добавлен list2в list1, поэтому вы получите новый список с len(list1) + len(list2)элементами.

Операторы, которые имеют смысл только при поэлементном применении, например &, raise 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) == Trueif listне пусто, а Falseесли оно пусто. Для поэлементных операций numpy существует некоторая неоднозначность, следует ли проверять, оценивается ли какой-либо элемент Trueили все элементы оцениваются True. Поскольку оба, возможно, верны, numpy не угадывает и вызывает, ValueErrorкогда bool()(косвенно) вызывается для массива.

Knbk
источник
0

Хороший вопрос. Подобно вашему наблюдению о примерах 1 и 4 (или я должен сказать 1 и 4 :)) над логическими andпобитовыми &операторами, я испытал на sumоператоре. Numpy sumи py также sumведут себя по-разному. Например:

Предположим, что "mat" представляет собой массив размером 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, такая как sum (mat), составляет только по оси. Увидеть ниже:

np.sum(mat)  ## --> gives 325
sum(mat)     ## --> gives array([55, 60, 65, 70, 75])
С.С.Прабху
источник