O que x [x <2] = 0 significa em Python?

85

Me deparei com um código com uma linha semelhante a

x[x<2]=0

Brincando com variações, ainda estou preso no que essa sintaxe faz.

Exemplos:

>>> 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]
aberger
fonte
7
nunca faz sentido fazer isso com uma lista.
dbliss
12
Isso só faz sentido com matrizes NumPy ou objetos semelhantes, que se comportam de maneira completamente diferente do comportamento em seus experimentos ou do comportamento baseado em lista explicado em qualquer uma das respostas.
user2357112 suporta Monica
11
Observe que isso não funciona no Python 3. Os tipos só podem ser comparados quando a comparação faz sentido. Em Python 3, este exemplo joga TypeError: unorderable types: list() < int().
Morgan Thrapp
2
Muito pouca informação. Deveria ter mencionado que a matriz é uma matriz numpy.
lmaooooo
3
Estou chocado que tenha recebido tantos votos positivos (embora seja realmente uma boa pergunta para o formato SO).
PascalVKooten

Respostas:

120

Isso só faz sentido com matrizes NumPy . O comportamento com listas é inútil e específico para Python 2 (não Python 3). Você pode querer verificar novamente se o objeto original era de fato um array NumPy (veja mais abaixo) e não uma lista.

Mas em seu código aqui, x é uma lista simples.

Desde a

x < 2

é falso, ou seja, 0, portanto

x[x<2] é x[0]

x[0] é alterado.

Por outro lado, x[x>2]é x[True]oux[1]

Então, x[1]é alterado.

Por que isso acontece?

As regras de comparação são:

  1. Quando você pede duas strings ou dois tipos numéricos, a ordenação é feita da maneira esperada (ordenação lexicográfica para string, ordenação numérica para inteiros).

  2. Quando você pede um tipo numérico e um não numérico, o tipo numérico vem primeiro.

  3. Quando você ordena dois tipos incompatíveis onde nenhum é numérico, eles são ordenados pela ordem alfabética de seus nomes de tipo:

Então, temos a seguinte ordem

numérico <lista <string <tupla

Veja a resposta aceita para Como o Python compara string e int? .

Se x for um array NumPy , a sintaxe faz mais sentido por causa da indexação do array booleano . Nesse caso, x < 2não é booleano; é uma matriz de booleanos que representam se cada elemento de xera menor que 2. x[x < 2] = 0então seleciona os elementos de xque eram menores que 2 e define essas células como 0. Veja Indexação .

>>> 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
trans1st0r
fonte
11
Dado que o OP diz especificamente "Encontrei um código como este ...", acho que sua resposta descrevendo a indexação booleana numpy é muito útil - pode valer a pena apontar que se o OP rolar para cima o código que examinou, eles ' Quase certamente verei um importpara entorpecido.
J Richard Snape
2
Ainda é uma maneira excessivamente inteligente de fazer isso, certo? (Em comparação com, digamos [0 if i < 2 else i for i in x],.) Ou esse estilo é encorajado no Numpy?
Tim Pederick de
6
@TimPederick: Usar compreensões de lista com NumPy é uma péssima ideia. É dezenas a centenas de vezes mais lento, não funciona com matrizes dimensionais arbitrárias, é mais fácil confundir os tipos de elementos e cria uma lista em vez de uma matriz. A indexação de matriz booleana é completamente normal e esperada em NumPy.
user2357112 suporta Monica de
@TimPederick Além do impacto no desempenho, também é provável que quem escreveu o código pretendia continuar usando um array numpy. x[x<2]retornará um array numpy, enquanto [0 if i<2 else i for i in x]retorna uma lista. Isso ocorre porque x[x<2]é uma operação de indexação (referida em numpy / scipy / pandas como uma operação de corte devido à capacidade de mascarar dados), enquanto a compreensão de lista é uma nova definição de objeto. Consulte a indexação NumPy
Michael Delgado
45
>>> x = [1,2,3,4,5]
>>> x<2
False
>>> x[False]
1
>>> x[True]
2

O bool é simplesmente convertido em um número inteiro. O índice é 0 ou 1.

Karoly Horvath
fonte
7
Você pode mencionar que xe 2são " ordenados de forma consistente, mas arbitrária " e que a ordem pode mudar em diferentes implementações Python.
Robᵩ
2
Eu também acrescentaria que essa é uma maneira inteligente de fazer as coisas e, em minha opinião, deve ser evitada. Faça isso explicitamente - o fato de que OP teve que fazer essa pergunta apóia meu ponto.
Kratenko
11
você pode adicionar mais detalhes, por quê x<2 == false?
Iłya Bursov
15
boolnão é convertido em um inteiro, a boolem Python é um inteiro
Antti Haapala
2
Apenas para esclarecer a declaração de @anttiHaapala para qualquer outra pessoa que vier, bool é uma subclasse de int.
porglezomp
14

O código original em sua pergunta funciona apenas no Python 2. Se xfor um listno Python 2, a comparação x < yé Falsese yfor um integer. Isso ocorre porque não faz sentido comparar uma lista com um inteiro. No entanto, no Python 2, se os operandos não são comparáveis, a comparação é baseada no CPython na ordem alfabética dos nomes dos tipos ; além disso, todos os números vêm primeiro em comparações de tipos mistos . Isso nem mesmo está explicitado na documentação do CPython 2, e diferentes implementações do Python 2 podem dar resultados diferentes. Isso é [1, 2, 3, 4, 5] < 2avaliado como Falseporque 2é um número e, portanto, "menor" que a listno CPython. Esta comparação mista foi eventualmenteconsiderado um recurso muito obscuro e foi removido no Python 3.0.


Agora, o resultado de <é um bool; e boolé uma subclasse deint :

>>> isinstance(False, int)
True
>>> isinstance(True, int)
True
>>> False == 0
True
>>> True == 1
True
>>> False + 5
5
>>> True + 5
6

Então, basicamente, você está pegando o elemento 0 ou 1, dependendo se a comparação é verdadeira ou falsa.


Se você tentar o código acima no Python 3, obterá TypeError: unorderable types: list() < int()devido a uma alteração no Python 3.0 :

Comparações de pedidos

Python 3.0 simplificou as regras para comparações de pedidos:

Os operadores de comparação ordenação ( <, <=, >=, >) levantar uma TypeErrorexceção quando os operandos não têm uma ordenação natural significativa. Assim, expressões como 1 < '', 0 > Noneou len <= lennão são mais válidas e, por exemplo, None < Noneaumenta em TypeErrorvez de retornar False. Um corolário é que classificar uma lista heterogênea não faz mais sentido - todos os elementos devem ser comparáveis ​​entre si. Observe que isso não se aplica aos operadores ==e !=: objetos de diferentes tipos incomparáveis ​​sempre se comparam de forma desigual.


Existem muitos tipos de dados que sobrecarregam os operadores de comparação para fazer algo diferente (dataframes de pandas, matrizes de numpy). Se o código que você estava usando fazia outra coisa, era porque nãox era umlist , mas uma instância de alguma outra classe com operador <substituído para retornar um valor que não é um bool; e este valor foi então tratado especialmente por x[](aka __getitem__/ __setitem__)

Antti Haapala
fonte
6
+FalseOlá, Perl, ei JavaScript, como estão?
gato
@cat em Javascript, Perl, converte o valor em número. Em Python, é para UNARY_POSITIVEopcode que chama o__pos__
Antti Haapala
Acho que você quis dizer em __setitem__vez de __getitem__em sua última seção. Além disso, espero que você não se importe que minha resposta tenha sido inspirada por essa parte de sua resposta.
MSeifert
Não, eu quis dizer e estava pensando, __getitem__embora igualmente poderia ter sido __setitem__e__delitem__
Antti Haapala
9

Isso tem mais uma utilidade: código de golfe. O golfe do código é a arte de escrever programas que resolvam alguns problemas com o mínimo possível de bytes de código-fonte.

return(a,b)[c<d]

é aproximadamente equivalente a

if c < d:
    return b
else:
    return a

exceto que tanto a quanto b são avaliados na primeira versão, mas não na segunda versão.

c<davalia para Trueou False.
(a, b)é uma tupla.
A indexação em uma tupla funciona como a indexação em uma lista: (3,5)[1]== 5.
Trueé igual a 1e Falseé igual a 0.

  1. (a,b)[c<d]
  2. (a,b)[True]
  3. (a,b)[1]
  4. b

ou para False:

  1. (a,b)[c<d]
  2. (a,b)[False]
  3. (a,b)[0]
  4. a

Há uma boa lista na rede de troca de pilha de muitas coisas desagradáveis ​​que você pode fazer no python para economizar alguns bytes. /codegolf/54/tips-for-golfing-in-python

Embora no código normal isso nunca deva ser usado, no seu caso significaria que xatua tanto como algo que pode ser comparado a um inteiro e como um contêiner que suporta o fatiamento, o que é uma combinação muito incomum. Provavelmente é um código Numpy, como outros apontaram.

Filip Haglund
fonte
6
Code Golf is the art of writing programs: ')
gato
1
Pequenos detalhes: o bool não é convertido em um int, ele apenas é um (veja as outras respostas)
cat
6

Em geral, pode significar qualquer coisa . Ele já foi explicado o que significa que se xé um listou numpy.ndarraymas em geral ele só depende de como os operadores de comparação ( <, >...) e também como o get / set-item ( [...]-syntax) são implementadas.

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!

Porque:

  • x < value é equivalente a x.__lt__(value)
  • x[value] é (aproximadamente) equivalente a x.__getitem__(value)
  • x[value] = othervalueé (também aproximadamente) equivalente a x.__setitem__(value, othervalue).

Isto pode ser personalizado para fazer qualquer coisa que quiser. Apenas como exemplo (imita uma indexação bit numpys-boolean):

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

Então, agora vamos ver o que acontece se você usá-lo:

>>> 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])

Observe que esta é apenas uma possibilidade. Você é livre para implementar quase tudo o que quiser.

MSeifert
fonte
Eu diria que usar qualquer coisa realmente é muito geral para um comportamento explicável logicamente, como a resposta aceita.
PascalVKooten
@PascalvKooten Você discorda de "qualquer coisa" ou da resposta generalizada? Acho que é um ponto importante a ser destacado, porque o comportamento mais lógico em python é apenas por convenção.
MSeifert