Python NumPy奇怪的布尔运算行为

3
为什么在Python / NumPy中会出现这种情况:
from numpy import asarray
bools=asarray([False,True])

print(bools)
[False True]

print(1*bools, 0+bools, 0-bools)    # False, True are valued as 0, 1
[0 1] [0 1] [ 0 -1]

print(-2*bools, -bools*2)           # !? expected same result!  :-/
[0 -2] [2 0] 

print(-bools)                       # this is the reason!
[True False]

我认为-bools返回logical_not(bools)很奇怪,因为在所有其他情况下,行为都是“算术”,而不是“逻辑”。

想要使用布尔数组作为0/1掩码(或“特征函数”)的人被迫使用某种倒转表达式,例如(0-bools)(-1)*bools,如果忘记了这一点,就很容易出现错误。

为什么会这样,以及获得所需行为的最佳可接受方式是什么? (除了注释之外)


1
我不明白为什么有人会使用布尔数组作为掩码,而不是更直观的 (0,1),但我确实看到了一点。然而,当执行 -2 * False 时,期望的行为是什么? - Ma0
在Python中,一切都是逻辑的。例如,“blabla”既可以是True(因此有点布尔值),又可以是字符串。 - Ma0
@Ev.Kounis,我在编写以下代码时偶然发现了一个问题:delExpDecay=lambda t,d,a,tau: a*exp(-(t<d)*(t-d)/tau),这是一个延迟指数衰减函数,但它并不能正常工作。如果你改变操作的顺序,它居然可以正常工作,这看起来非常奇怪(乘法应该是可交换的,不是吗?)。因此,我将问题简化为最简单的问题。 - lurix66
2个回答

2

这全部都与操作符顺序和数据类型有关。

>>> import numpy as np
>>> B = np.array([0, 1], dtype=np.bool)
>>> B
array([False,  True], dtype=bool)

使用 numpy,布尔数组被视为布尔数组。应用于它们的每个操作都会首先尝试保持数据类型。这就是为什么:
>>> -B
array([ True, False], dtype=bool)

并且。
>>> ~B
array([ True, False], dtype=bool)

这些函数是等价的,可以返回元素的逐个取反。然而,请注意使用-B会产生警告,因为该函数已经过时。

当您使用以下内容时:

>>> B + 1
array([1, 2])

B1在幕后首先被转换为相同的数据类型。在数据类型提升中,boolean数组总是被强制转换为numeric数组。在上述情况中,B被转换为int,这类似于:

>>> B.astype(int) + 1
array([1, 2])

在你的例子中:

>>> -B * 2
array([2, 0])

首先,数组B通过运算符-被取反,然后乘以2。可以通过显式数据转换或添加括号来确保正确的操作顺序来采用所需的行为:

>>> -(B * 2)
array([ 0, -2])

或者

>>> -B.astype(int) * 2
array([ 0, -2])

请注意,B.astype(int) 可以被 B.view(np.int8) 替换而不需要数据复制。因为布尔值用字符表示,因此具有 8 位,可以使用 .view 方法将数据视为整数而无需转换。
>>> B.view(np.int8)
array([0, 1], dtype=int8)

简而言之,B.view(np.int8)B.astype(yourtype) 始终会确保 B 是一个数值数组,范围在 [0,1] 之间。


感谢您提供全面的解释,特别是注意到波浪号~。如果我们有一个波浪号运算符,为什么还要让一元减号-作为重复操作符呢?我认为更合理的是,一元加号+和一元减号-都产生算术转换,就像0+B0-B这样的操作一样。(请参见我上面对自己问题的评论) - lurix66
这只是一个设计决策,显然他们后悔了,因为它已经被弃用并将在未来被删除。当使用“-”作为二元运算符“0-B”或“B-0”时,它可以正常工作(如您所期望的那样),问题是当您将“-”用作一元运算符“-B”时,它不像在数值数组中一样被翻译为“-1 * B”,而是被翻译为“~B”(或“not B”)。同样,除非numpy决定停止支持“-”一元运算符作为否定运算符,否则我们无法做任何事情,因此我们必须等待或同时更加小心 :P - Imanol Luengo

0
Numpy数组是同质的 - 对于给定的数组,所有元素具有相同的类型,并且数组对象存储了该类型。当您使用TrueFalse创建一个数组时,它是一个bool类型的数组,并且运算符会按此数组进行处理。因此,在正常bool情况下会发生逻辑否定的情况下,您会得到逻辑否定的结果,这并不令人惊讶。当您将数组用于整数运算时,它们将被转换为1和0。在所有示例中,这些都是更为反常的情况,也就是说,在良好的代码中不能依赖这种行为。
正如评论中所建议的那样,如果您想用0和1的数组进行数学运算,最好只是制作一个由0和1组成的数组。但是,根据您想要做什么,您可能更好地考虑使用像numpy.where()这样的函数。

如果一行代码简单易懂,我认为它是有价值的。在我的看法中,使用 numpy.where() 看起来不如使用显而易见的等价方式——布尔掩码值为 True 时为 1,False 时为 0——简单。 - lurix66

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接