在Python中,x[x < 2] = 0是什么意思?

88

我发现一些代码中有一行类似于:

x[x<2]=0

在尝试各种变化后,我仍然困惑于这个语法的作用。

示例:

>>> x = [1,2,3,4,5]
>>> x[x<2]
1
>>> x[x<3]
1
>>> x[x>2]
2
>>> x[x<2]=0
>>> x
[0, 2, 3, 4, 5]

7
用列表来做这件事情从来就没有意义。 - abcd
12
这只适用于NumPy数组或类似对象,它们的行为与你的实验结果或答案解释中所述的基于列表的行为完全不同。 - user2357112
11
请注意,这在 Python 3 中不起作用。只有当比较有意义时,类型才能进行比较。在 Python 3 中,此示例会引发“TypeError: unorderable types: list() < int()”错误。 - Morgan Thrapp
2
信息太少了。应该提到这个数组是numpy数组。 - lmaooooo
3
我很震惊这篇文章得到了这么多的赞(虽然从 Stack Overflow 格式来看确实是一个好问题)。 - PascalVKooten
显示剩余2条评论
5个回答

122

这只对 NumPy 数组 有意义。使用列表没有用处,并且只适用于 Python 2(不适用于 Python 3)。您可能需要仔细检查原始对象是否确实是 NumPy 数组(请参见下面的更多信息),而不是列表。

但是,在您的代码中,x 是一个简单的列表。

因为

x < 2

为假即0,因此 x[x<2] 的结果是 x[0]

x[0] 被改变了。

相反地,x[x>2] 的结果是 x[True]x[1]

所以,x[1] 被改变了。

为什么会发生这种情况?

比较规则如下:

  1. 当你比较两个字符串或两个数值类型时,按预期的方式进行排序(按字典顺序排序的字符串,按整数进行数值排序)。

  2. 当你比较一个数值类型和一个非数值类型时,数值类型排在前面。

  3. 当你比较两个不兼容的类型,其中没有一个是数值类型时,按它们类型名称的字母顺序进行排序:

因此,我们有以下顺序:

数值 < 列表 < 字符串 < 元组

参见How does Python compare string and int?的接受答案。

如果x是NumPy数组,那么由于布尔数组索引,语法更有意义。在这种情况下,x<2根本不是布尔值;它是一个由布尔值组成的数组,表示x的每个元素是否小于2。x[x<2] = 0然后选择小于2的x的元素,并将这些单元格设置为0。请参见Indexing

>>> x = np.array([1., -1., -2., 3])
>>> x < 0
array([False,  True,  True, False], dtype=bool)
>>> x[x < 0] += 20   # All elements < 0 get increased by 20
>>> x
array([  1.,  19.,  18.,   3.]) # Only elements < 0 are affected

11
鉴于提问者明确表示“我发现了这样的代码...”,我认为您关于numpy布尔索引的答案非常有用 - 也许值得指出的是,如果提问者向上滚动所查看的代码,他们几乎肯定会看到一个导入 numpy 的语句。 - J Richard Snape
2
这样做还是过于聪明了,不是吗?(与[0 if i < 2 else i for i in x]相比)或者这是Numpy中鼓励的风格吗? - Tim Pederick
7
使用NumPy的列表推导式是一个相当糟糕的想法。它比布尔数组索引慢数十到数百倍,不能与任意维度的数组一起使用,容易弄错元素类型,并且生成的是列表而不是数组。在NumPy中,布尔数组索引是完全正常和预期的。 - user2357112
@TimPederick 除了性能下降之外,编写代码的人很可能打算继续使用numpy数组。x[x<2]将返回一个numpy数组,而[0 if i<2 else i for i in x]则返回一个列表。这是因为x[x<2]是一个索引操作(在numpy/scipy/pandas中称为切片操作,因为它可以掩盖数据),而列表推导式是一个新对象定义。请参见NumPy索引 - Michael Delgado

45
>>> x = [1,2,3,4,5]
>>> x<2
False
>>> x[False]
1
>>> x[True]
2

布尔类型会被简单地转换为整数。索引只可能是0或1。


7
你可以提到 x2 是“有序一致但任意的”,并且在不同的Python实现中排序可能会发生变化。 - Robᵩ
是的。Python3:http://ideone.com/1Cw4gb 和 https://dev59.com/wXA75IYBdhLWcg3weZDu - Karoly Horvath
2
我还要补充的是,这是一种聪明的做法,但在我看来应该避免。要明确地做 - OP不得不问这个问题支持了我的观点。 - kratenko
11
你能否添加更多细节,解释为什么x<2==false - Iłya Bursov
15
在Python中,bool不会转换为整数,而是bool本身就是一个整数。 - Antti Haapala -- Слава Україні
2
只是为了澄清@AnttiHaapala的陈述,对于其他人来说,“bool”是“int”的子类。 - porglezomp

14

你问题中的原始代码仅适用于Python 2。如果x是Python 2中的list,则比较x < y如果y是一个integer,则为False。这是因为将列表与整数进行比较没有意义。然而,在Python 2中,如果操作数不可比较,则基于类型名称按字母顺序排序的方式进行比较;此外,在混合类型比较中所有数字都排在前面。这甚至未在CPython 2的文档中详细说明,不同的Python 2实现可能会产生不同的结果。因此,[1, 2, 3, 4, 5] < 2的结果为False,因为2是一个数字,因此在CPython中比list“小”。这种混合比较最终被认为是过于晦涩的功能,并在Python 3.0中被删除。


现在,< 的结果是一个 bool; 而 boolint 的一个子类:
>>> isinstance(False, int)
True
>>> isinstance(True, int)
True
>>> False == 0
True
>>> True == 1
True
>>> False + 5
5
>>> True + 5
6

基本上,您将获取元素0或1,具体取决于比较结果是true还是false。
如果您在Python 3中尝试以上代码,将会出现TypeError: unorderable types: list() < int()的错误,这是由于Python 3.0中的更改所导致的: 比较操作 Python 3.0 简化了比较操作的规则:
比较操作符(<<=>=>)在操作数没有有意义的自然排序时会引发TypeError异常。因此,表达式如1 < ''0 > Nonelen <= len不再有效,并且例如None < None引发TypeError而不是返回False。一个推论是对于异构列表进行排序不再有意义——所有元素必须可相互比较。请注意,这不适用于==!=运算符:不同类型的对象始终与彼此不可比较。

有许多数据类型会重载比较运算符以执行不同的操作(例如pandas中的数据框架和numpy的数组)。如果你使用的代码执行了其他操作,那么这是因为x不是一个list,而是另一个类的实例,该类重载了运算符<以返回一个不是bool的值;然后这个值被x[](也称为__getitem__/__setitem__)特殊处理。


6
+False 你好 Perl,嘿 JavaScript,你们俩怎么样? - cat
在Javascript和Perl中,@cat将值转换为数字。而在Python中,它是用于“UNARY_POSITIVE”操作码的,该操作码调用了“__pos__”。 - Antti Haapala -- Слава Україні
我认为你在最后一部分想表达的是 __setitem__ 而不是 __getitem__。另外,希望你不介意我的回答受到了你回答中那部分的启发。 - MSeifert
不,我的意思是并且我在想的是__getitem__,虽然同样也可以是__setitem____delitem__ - Antti Haapala -- Слава Україні

9

这里还有一个用途:代码高尔夫。代码高尔夫是编写尽可能少的源代码字节解决某些问题的艺术。

return(a,b)[c<d]

大致等同于

if c < d:
    return b
else:
    return a

除了第一个版本中都会对a和b进行评估之外,第二个版本不会对它们进行评估。 d<d的计算结果为True或False。
(a, b)是一个元组。
在元组上进行索引的方式与列表相同:(3,5)[1] == 5。
True等于1,False等于0。
  1. (a,b)[c<d]
  2. (a,b)[True]
  3. (a,b)[1]
  4. b
或者当False时:
  1. (a,b)[c<d]
  2. (a,b)[False]
  3. (a,b)[0]
  4. a
在堆栈交换网络上有一个很好的列表,其中包含许多可以对Python进行的恶意操作,以节省一些字节数。 https://codegolf.stackexchange.com/questions/54/tips-for-golfing-in-python 尽管在正常代码中永远不应该使用这种方法,在您的情况下,这意味着x既可以被比较为整数,又可以作为支持切片的容器,这是一种非常不寻常的组合。它可能是Numpy代码,正如其他人所指出的那样。

6
"Code Golf" 是编写程序的一种艺术形式。':)' - cat
1
小问题:bool类型并不是被强制转换为int类型,它本身就是int类型(请参见其他答案)。 - cat

6
一般而言,它可以指任何东西。虽然已经解释了如果x是list或numpy.ndarray,则其含义,但总的来说,这取决于比较运算符(如<、>等)以及get/set-item( [...] 语法)的实现方式。
x.__getitem__(x.__lt__(2))      # this is what x[x < 2] means!
x.__setitem__(x.__lt__(2), 0)   # this is what x[x < 2] = 0 means!

原因是:

  • x < value 相当于 x.__lt__(value)
  • x[value] (大致)相当于 x.__getitem__(value)
  • x[value] = othervalue (也大致)相当于 x.__setitem__(value, othervalue).

这可以被定制以做任何你想做的事情。只是举个例子(模仿了一些numpys布尔索引):

class Test:
    def __init__(self, value):
        self.value = value

    def __lt__(self, other):
        # You could do anything in here. For example create a new list indicating if that 
        # element is less than the other value
        res = [item < other for item in self.value]
        return self.__class__(res)

    def __repr__(self):
        return '{0} ({1})'.format(self.__class__.__name__, self.value)

    def __getitem__(self, item):
        # If you index with an instance of this class use "boolean-indexing"
        if isinstance(item, Test):
            res = self.__class__([i for i, index in zip(self.value, item) if index])
            return res
        # Something else was given just try to use it on the value
        return self.value[item]

    def __setitem__(self, item, value):
        if isinstance(item, Test):
            self.value = [i if not index else value for i, index in zip(self.value, item)]
        else:
            self.value[item] = value

那么现在让我们看看如果您使用它会发生什么:

>>> a = Test([1,2,3])
>>> a
Test ([1, 2, 3])
>>> a < 2  # calls __lt__
Test ([True, False, False])
>>> a[Test([True, False, False])] # calls __getitem__
Test ([1])
>>> a[a < 2] # or short form
Test ([1])

>>> a[a < 2] = 0  # calls __setitem__
>>> a
Test ([0, 2, 3])

请注意,这只是一种可能性。您可以自由地实现几乎所有您想要的东西。


我认为在像被接受的答案这样可以逻辑解释的行为中使用“任何东西”实在太笼统了。 - PascalVKooten
@PascalvKooten 你不同意“anything”还是这个泛化的答案?我认为这是一个重要的观点,因为在Python中,大多数逻辑行为仅仅是约定俗成的。 - MSeifert

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