如何解释列表与NumPy数组上的布尔和按位操作的行为差异?
我对&
和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!
This answer和this answer帮助我理解了and
是布尔运算,而&
是位运算。
我阅读了有关bitwise operations的内容以更好地理解这个概念,但我很难使用这些信息来理解我上面的4个例子。
示例4引导我得到了我想要的输出,所以这很好,但是我仍然对何时/如何/为什么应该使用and
和&
感到困惑。为什么列表和NumPy数组在使用这些运算符时表现不同?
谁能帮助我理解布尔运算和位运算之间的区别,解释为什么它们处理列表和NumPy数组的方式不同?
8条答案
按热度按时间mm5n2pyu1#
and
测试两个表达式在逻辑上是否都是True
,而&
(当与True
/False
值一起使用时)测试两个表达式是否都是True
。在Python中,空的内置对象通常在逻辑上被视为
False
,而非空的内置对象在逻辑上被视为True
。这有助于常见的用例,即如果列表为空,则需要执行某些操作,如果列表不是空的,则需要执行其他操作。注意,这意味着列表[False]在逻辑上是True
:因此在示例1中,第一个列表是非空的,因此逻辑上为
True
,因此and
的真值与第二个列表的真值相同。(在我们的例子中,第二个列表是非空的,因此逻辑上是True
,但是识别它需要不必要的计算步骤。例如,列表不能以按位方式有意义地组合,因为它们可以包含任意不同的元素。可以按位组合的内容包括:真与假,整数。
相比之下,NumPy对象支持矢量化计算。也就是说,它们允许您对多个数据片段执行相同的操作。
示例3失败,因为NumPy数组(长度> 1)没有真值,因为这会防止基于向量的逻辑混淆。
示例4简单地是向量化位
and
操作。底线
and
。numpy
与&
。5jvtdoz22#
关于
list
首先是非常重要的一点,一切都将遵循(我希望)。
在普通的Python中,
list
在任何方面都没有什么特别之处(除了有一个可爱的构造语法,这主要是一个历史性的意外)。一旦创建了一个列表[3,2,6]
,它就只是一个普通的Python对象,就像数字3
、集合{3,7}
或函数lambda x: x+5
一样。(Yes,它支持更改元素,支持迭代,以及其他许多东西,但这就是类型:它支持一些操作,而不支持另一些操作。int支持提升到幂,但这并不使它变得非常特殊-它只是int的一部分。lambda支持调用,但这并不意味着它很特别--毕竟,这就是lambda的用途:)。
关于
and
and
不是一个操作符(你可以称之为“operator”,但你也可以称之为“for”操作符:)。Python中的运算符是(通过)在某种类型的对象上调用的方法,通常作为该类型的一部分编写。一个方法没有办法保存它的一些操作数的求值,但是and
可以(并且必须)这样做。这样做的结果是
and
不能重载,就像for
不能重载一样。它是完全通用的,并通过指定的协议进行通信。你能做的是定制你的那部分协议,但这并不意味着你可以完全改变and
的行为。方案为:想象一下Python解释“a和B”(实际上并不是这样,但它有助于理解)。当涉及到“and”时,它会查看它刚刚评估的对象(a),并询问它:是真的吗?(NOT:你是
True
吗?)如果您是a的类的作者,您可以自定义此答案。如果a
回答“否”,and
(完全跳过B,它根本不被评估,并且)说:a
是我的结果(不是:假的是我的结果)。如果
a
没有回答,and
会询问它:您的长度是多少?(同样,您可以作为a
类的作者来定制这个)。如果a
的答案是0,and
的操作与上面的相同-认为它为false(NOTFalse),跳过b,并给出a
作为结果。如果
a
对第二个问题(“你的长度是多少”)的回答不是0,或者根本没有回答,或者对第一个问题(“你是真的吗”)的回答是“是”,and
计算B,并说:b
是我的结果。请注意,它不会***询问b
任何问题。另一种说法是
a and b
几乎与b if a else a
相同,除了a只计算一次。现在拿着笔和纸坐几分钟,说服自己当{a,B}是{True,False}的子集时,它的工作方式与布尔运算符的工作方式完全相同。但我希望我已经说服你们,它更通用,正如你们将看到的,这种方式更有用。
把这两个放在一起
现在我希望你能理解你的例子1。
and
不关心mylist 1是数字、列表、lambda还是Argmhbl类的对象。它只关心mylist 1对协议问题的回答。当然,对于长度问题,mylist 1的答案是5,所以和返回mylist 2。就是这样它与mylist 1和mylist 2的元素没有任何关系--它们不会进入图片的任何地方。示例二:
list
上的&
另一方面,
&
是一个像任何其他运算符一样的运算符,例如+
。可以通过在该类上定义一个特殊的方法来为该类型定义它。int
将其定义为按位的“and”,bool将其定义为逻辑的“and”,但这只是一个选项:例如,集合和一些其他对象(如Dict键视图)将其定义为集合交集。list
只是没有定义它,可能是因为Guido没有想到任何明显的定义方法。numpy
另一方面:-D,numpy数组 * 是 * 特殊的,或者至少他们试图成为。当然,numpy.array只是一个类,它不能以任何方式覆盖
and
,所以它做了下一个最好的事情:当被问到“你是真的吗”时,numpy.array会引发ValueError,实际上是说“请重新表述这个问题,我对真理的看法不适合你的模型”。(注意ValueError消息并没有提到and
--因为numpy.array不知道 * 谁 * 在问它这个问题;它只讲真理)。对于
&
,情况完全不同。numpy.array可以按照自己的意愿定义它,并且它定义的&
与其他运算符一致:所以你最终得到了你想要的。HTH,
9avjhtql3#
短路布尔运算符(
and
,or
)不能被重写,因为没有令人满意的方法来做到这一点,而不引入新的语言功能或牺牲短路。你可能知道,也可能不知道,它们计算第一个操作数的真值,并根据该值,计算并返回第二个参数,或者不计算第二个参数并返回第一个参数:请注意,返回的是实际操作数(计算的结果),而不是其真值。
自定义其行为的唯一方法是覆盖
__nonzero__
(在Python 3中重命名为__bool__
),因此您可以影响返回的操作数,但不会返回不同的内容。列表(和其他集合)被定义为当它们包含任何东西时是“truthy”,当它们为空时是“falsey”。NumPy数组拒绝这个概念:对于他们所针对的用例,两种不同的真理概念是常见的:(1)是否有任何元素为真,以及(2)是否所有元素都为真。由于这两个完全(和无声)不兼容,并且没有一个明显更正确或更常见,NumPy拒绝猜测并要求您显式使用
.any()
或.all()
。&
和|
(顺便说一句,还有not
)* 可以 * 被完全覆盖,因为它们不会短路。它们在被覆盖时可以返回任何东西,NumPy很好地利用了这一点来进行元素操作,就像它们实际上对任何其他标量操作所做的那样。另一方面,列表不会在它们的元素之间传播操作。就像mylist1 - mylist2
没有任何意义,mylist1 + mylist2
的意义完全不同一样,列表也没有&
运算符。afdcj2ne4#
示例1:
这就是and运算符的工作方式。
换句话说,因为
mylist1
不是False
,所以表达式的结果是mylist2
。(只有空列表的计算结果为False
。)示例二:
&
运算符用于按位的and,正如你提到的。按位运算只对数字起作用。a & B 的结果是由在 a 和 b 中均为1的位中的1组成的数。例如:使用二进制文字(与上面的数字相同)更容易看到发生了什么:
按位运算在概念上类似于布尔(真值)运算,但它们只对位起作用。
根据我的车的几份证词
1.我的车是红色的
1.我的车有轮子
这两个语句的逻辑“与”是:
(is我的车是红色的?)和(汽车有轮子吗?)=> logical true of false value
这两个都是真的,至少对我的车来说是这样。因此,整个语句的值在逻辑上是真的。
这两个语句的按位“与”有点模糊:
(the语句'my car is red'的数值)&(语句'my car has wheels'的数值)=> number
如果python知道如何将语句转换为数值,那么它将这样做并计算两个值的按位与。这可能会让你认为
&
和and
是可以互换的,但是和上面的例子一样,它们是不同的东西。此外,对于无法转换的对象,您将仅获得TypeError
。实施例3和4:
Numpy实现了数组的算术运算:
ndarray上的算术和比较操作被定义为逐元素操作,并且通常产生ndarray对象作为结果。
但不实现数组的逻辑操作,因为你can't overload logical operators in python。这就是为什么示例3不起作用,但示例4起作用。
下面是
and
与&
的比较:使用and
。按位操作用于检查数字的结构(设置了哪些位,未设置哪些位)。这类信息主要用于低级操作系统接口(例如unix permission bits)。大多数python程序不需要知道这些。
然而,逻辑运算(
and
、or
、not
)一直在使用。kx7yvsdv5#
1.在Python中,
X and Y
的表达式返回Y
,假设bool(X) == True
或X
或Y
中的任何一个的计算结果为False,例如:1.按位运算符根本没有为列表定义。但它是为整数定义的-对数字的二进制表示进行操作。考虑16(01000)和31(11111):
[False, False]
应该等于True
。在这里,它覆盖了一个标准的Python行为,即:任何带有len(collection) == 0
的空集合都是False
。1.可能是NumPy数组的&运算符的预期行为。
jchrr9hc6#
对于第一个例子,基于django的文档
它总是返回第二个列表,实际上,对于Python来说,非空列表被视为True值,因此Python返回'last' True值,因此第二个列表
4zcjmb1e7#
使用Python list的操作在 list 上操作。
list1 and list2
将检查list1
是否为空,如果为空则返回list1
,如果为空则返回list2
。list1 + list2
将把list2
追加到list1
,因此您将获得一个包含len(list1) + len(list2)
元素的新列表。只有在按元素应用时才有意义的运算符(如
&
)会引发TypeError
,因为如果不循环元素,则不支持按元素操作。Numpy数组支持 element-wise 操作。
array1 & array2
将计算array1
和array2
中每个相应元素的按位或。array1 + array2
将计算array1
和array2
中每个对应元素的和。这不适用于
and
和or
。array1 and array2
本质上是以下代码的简写:为此,您需要对
bool(array1)
进行良好的定义。对于Python列表上使用的全局操作,定义是如果list
不为空,则为bool(list) == True
,如果为空,则为False
。对于numpy的元素操作,是否检查任何元素的计算结果为True
,或者所有元素的计算结果为True
,有一些明确性。因为两者都可以说是正确的,所以numpy不会猜测,当bool()
在数组上被(间接)调用时,它会引发ValueError
。nvbavucw8#
问得好与您对逻辑
and
位&
运算符的示例1和4(或者我应该说1和4:))的观察类似,我在sum
运算符上进行了体验。numpysum
和pysum
的行为也不同。例如:假设“mat”是一个numpy 5x5 2d数组,如:
然后numpy.sum(mat)给出整个矩阵的总和。而Python中的内置sum,如sum(mat),仅沿着轴求和。参见下文: