'and' (布尔值) vs '&' (按位) - 为什么列表和 numpy 数组的行为有所不同?
- 2024-12-19 09:23:00
- admin 原创
- 138
问题描述:
如何解释列表和 NumPy 数组上的布尔和按位运算行为的差异?
我对Python 中&
vs的适当用法感到困惑and
,如以下示例所示。
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,列表无法以按位方式有意义地组合,因为它们可以包含任意不同的元素。可以按位组合的事物包括:True 和 False、整数。
相比之下,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
不是运算符(您可以将其称为“运算符”,但您也可以将“for”称为运算符:)。Python 中的运算符(通过)对某种类型的对象调用的方法实现,通常作为该类型的一部分编写。方法无法保存对其某些操作数的求值,但and
可以(并且必须)这样做。
其结果是and
不能被重载,就像for
不能被重载一样。它是完全通用的,并通过指定的协议进行通信。您可以做的是自定义协议的一部分,但这并不意味着您可以完全改变 的行为and
。协议是:
想象一下 Python 解释“a and b”(这实际上不会以这种方式发生,但有助于理解)。当遇到“and”时,它会查看刚刚评估的对象(a),并询问它:你是真的吗?(不是:你是吗True
?)如果您是 a 类的作者,您可以自定义此答案。如果a
回答“否”,and
(完全跳过 b,根本不进行评估,并且)说:a
是我的结果(不是:False 是我的结果)。
如果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 是数字、列表、lambda 还是 Argmhbl 类的对象。它只关心 mylist1 对协议问题的回答。当然,mylist1 对长度问题的回答是 5,所以返回 mylist2。就是这样。它与 mylist1 和 mylist2 的元素无关 - 它们不会出现在任何地方。
&
第二个例子:list
另一方面,&
是一个与其他运算符一样的运算符,例如+
。可以通过在该类上定义特殊方法来为该类型定义它。int
将其定义为按位“与”,而 bool 将其定义为逻辑“与”,但这只是一种选择:例如,集合和一些其他对象(如字典键视图)将其定义为集合交集。list
只是没有定义它,可能是因为 Guido 没有想到任何明显的定义方法。
numpy
另一方面:-D,numpy 数组很特别,或者至少它们试图成为特别的。当然,numpy.array 只是一个类,它不能and
以任何方式覆盖,所以它做了第二件最好的事情:当被问到“你是真的吗”时,numpy.array 会引发 ValueError,实际上是在说“请重新表述问题,我对真相的看法不符合你的模型”。(请注意,ValueError 消息没有说明and
- 因为 numpy.array 不知道是谁在问它这个问题;它只是说明真相。)
对于&
,情况则完全不同。numpy.array 可以按自己的意愿定义它,并且它&
与其他运算符一致地定义:逐点。所以你最终会得到你想要的。
高血压,
解决方案 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__
(在 Python 3 中重命名为__bool__
),这样您就可以影响返回哪个操作数,但不会返回其他内容。列表(和其他集合)在包含任何内容时定义为“真”,为空时定义为“假”。
NumPy 数组拒绝接受这种观点:对于它们所针对的用例,有两种不同的真值概念:(1) 任何元素是否为真,以及 (2) 所有元素是否为真。由于这两者完全(且默默地)不兼容,并且没有一个明显更正确或更常见,因此 NumPy 拒绝猜测,并要求您明确使用.any()
或.all()
。
&
and |
(not
顺便说一下,和 )可以完全覆盖,因为它们不会短路。覆盖后它们可以返回任何值,NumPy 充分利用了这一点来执行元素级运算,就像它们对其他标量运算所做的那样。另一方面,列表不会在其元素之间广播运算。就像mylist1 - mylist2
不代表任何意思 和代表完全不同的东西一样,列表mylist1 + mylist2
没有运算符。&
解决方案 4:
示例 1:
这就是and运算符的工作方式。
x和y => 如果x为假,则为x,否则为y
换句话说,由于mylist1
不是False
,表达式的结果为mylist2
。(只有空列表的计算结果为False
。)
示例 2:
正如您所提到的,该&
运算符用于按位与。按位运算仅适用于数字。a & b的结果是由a和b中均为 1 的位中的 1 组成的数字。例如:
>>> 3 & 1
1
使用二进制文字(与上面相同的数字)更容易看到发生了什么:
>>> 0b0011 & 0b0001
0b0001
按位运算在概念上与布尔(真值)运算类似,但它们只对位起作用。
因此,给出一些关于我的车的陈述
我的车是红色的
我的车有轮子
这两个语句的逻辑“与”是:
(我的车是红色的吗?)和(车有轮子吗?)=> 逻辑真假值
至少对于我的车来说,这两个说法都是正确的。因此,从逻辑上讲,该陈述的价值是真实的。
这两个语句的按位“与”有点模糊:
(语句‘我的车是红色的’的数值)&(语句‘我的车有轮子’的数值)=> 数字
如果 python 知道如何将语句转换为数值,那么它将这样做并计算两个值的按位与。这可能会让您认为&
可以与 互换and
,但与上面的例子一样,它们是不同的东西。此外,对于无法转换的对象,您只会得到TypeError
。
示例 3 和 4:
Numpy实现数组的算术运算:
ndarray 上的算术和比较运算被定义为逐元素运算,并且通常会产生 ndarray 对象作为结果。
但没有实现数组的逻辑运算,因为你不能在 python 中重载逻辑运算符。这就是为什么示例三不起作用,而示例四起作用的原因。
因此,要回答您的and
vs&
问题:使用and
。
按位运算用于检查数字的结构(哪些位已设置,哪些位未设置)。此类信息主要用于低级操作系统接口(例如unix 权限位)。大多数 python 程序不需要知道这一点。
然而,逻辑运算(and
,,)却一直被使用。or
`not`
解决方案 5:
在 Python 中,如果或或中的任意一个计算结果为 False,则表达式
X and Y
将返回,例如:Y
`bool(X) == TrueX
Y`
True and 20
>>> 20
False and 20
>>> False
20 and []
>>> []
位运算符根本没有为列表定义。但它为整数定义 - 对数字的二进制表示进行操作。考虑 16 (01000) 和 31 (11111):
16 & 31
>>> 16
NumPy 不是通灵者,它不知道在逻辑表达式中你的意思是 eg
[False, False]
应该等于。在这方面,它覆盖了标准的 Python 行为,即:“任何带有is 的True
空集合”。len(collection) == 0
`False`这可能是 NumPy 数组的 & 运算符的预期行为。
解决方案 6:
对于第一个例子,基于django 的文档,
它将始终返回第二个列表,实际上,非空列表被视为 Python 的 True 值,因此 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]
解决方案 7:
使用 Python 列表的操作对列表进行操作。将list1 and list2
检查是否list1
为空,list1
如果为空则返回,list2
如果不是,list1 + list2
则将附加list2
到list1
,这样您将获得一个包含元素的新列表len(list1) + len(list2)
。
仅按元素应用时才有意义的运算符,例如&
,引发TypeError
,因为如果不循环遍历元素,则不支持按元素操作。
Numpy 数组支持逐元素运算。将计算和中array1 & array2
每个对应元素的按位或。将计算和中每个对应元素的总和。array1
`array2array1 + 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 不会猜测并引发。ValueError
`bool()`
解决方案 8:
好问题。与您对示例 1 和 4(或者我应该说 1 & 4 :))的观察类似,我对运算符也有所体验and
。numpy和py 的行为也不同。例如:&
`sumsum
sum`
假设“mat”是一个 numpy 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])