¿Qué significa x [x <2] = 0 en Python?

85

Encontré un código con una línea similar a

x[x<2]=0

Jugando con variaciones, todavía estoy atascado en lo que hace esta sintaxis.

Ejemplos:

>>> 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
fuente
7
nunca tiene sentido hacer esto con una lista.
dbliss
12
Esto solo tiene sentido con matrices NumPy u objetos similares, que se comportan de manera completamente diferente al comportamiento en sus experimentos o al comportamiento basado en listas explicado en cualquiera de las respuestas.
user2357112 apoya a Monica
11
Tenga en cuenta que esto no funciona en Python 3. Los tipos solo se pueden comparar cuando la comparación tiene sentido. En Python 3, este ejemplo arroja TypeError: unorderable types: list() < int().
Morgan Thrapp
2
Muy poca información. Debería haber mencionado que la matriz es una matriz numerosa.
lmaooooo
3
Me sorprende que haya recibido tantos votos positivos (aunque de hecho es una buena pregunta para el formato SO).
PascalVKooten

Respuestas:

120

Esto solo tiene sentido con matrices NumPy . El comportamiento con listas es inútil y específico de Python 2 (no Python 3). Es posible que desee volver a verificar si el objeto original era de hecho una matriz NumPy (ver más abajo) y no una lista.

Pero en su código aquí, x es una lista simple.

Ya que

x < 2

es falso, es decir, 0, por lo tanto

x[x<2] es x[0]

x[0] se cambia.

Por el contrario, x[x>2]es x[True]ox[1]

Entonces, x[1]se cambia.

¿Por qué pasó esto?

Las reglas de comparación son:

  1. Cuando ordena dos cadenas o dos tipos numéricos, el orden se realiza de la forma esperada (ordenamiento lexicográfico para cadenas, ordenamiento numérico para enteros).

  2. Cuando solicita un tipo numérico y uno no numérico, el tipo numérico es lo primero.

  3. Cuando ordena dos tipos incompatibles donde ninguno es numérico, se ordenan por orden alfabético de sus nombres de tipo:

Entonces, tenemos el siguiente orden

numérico <lista <cadena <tupla

Vea la respuesta aceptada para ¿Cómo compara Python string e int? .

Si x es una matriz NumPy , entonces la sintaxis tiene más sentido debido a la indexación de la matriz booleana . En ese caso, x < 2no es un booleano en absoluto; es una matriz de valores booleanos que representan si cada elemento de xera menor que 2. x[x < 2] = 0luego selecciona los elementos de xque eran menores que 2 y establece esas celdas en 0. Consulte Indexación .

>>> 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
fuente
11
Dado que el OP dice específicamente "Me encontré con un código como este ...", creo que su respuesta que describe la indexación booleana numpy es muy útil; podría valer la pena señalar que si el OP desplaza hacia arriba el código que miraron, ellos ' Es casi seguro que veré un importnumpy.
J Richard Snape
2
¿Seguramente sigue siendo una forma demasiado inteligente de hacerlo? (En comparación con, digamos [0 if i < 2 else i for i in x],.) ¿O es este estilo fomentado en Numpy?
Tim Pederick
6
@TimPederick: Usar listas de comprensión con NumPy es una idea bastante mala. Es decenas a cientos de veces más lento, no funciona con matrices de dimensiones arbitrarias, es más fácil estropear los tipos de elementos y crea una lista en lugar de una matriz. La indexación de matrices booleanas es completamente normal y se espera en NumPy.
user2357112 apoya a Monica
@TimPederick Además del impacto en el rendimiento, también es probable que quien escribió el código tuviera la intención de seguir usando una matriz numerosa. x[x<2]devolverá una matriz numpy, mientras que [0 if i<2 else i for i in x]devuelve una lista. Esto se debe a que x[x<2]es una operación de indexación (a la que se hace referencia en numpy / scipy / pandas como una operación de corte debido a la capacidad de enmascarar datos), mientras que la comprensión de la lista es una nueva definición de objeto. Ver indexación NumPy
Michael Delgado
45
>>> x = [1,2,3,4,5]
>>> x<2
False
>>> x[False]
1
>>> x[True]
2

El bool simplemente se convierte en un número entero. El índice es 0 o 1.

Karoly Horvath
fuente
7
Puede mencionar que xy 2están " ordenados de forma coherente pero arbitraria " y que el orden puede cambiar en diferentes implementaciones de Python.
Robᵩ
2
También añadiría que es una forma inteligente de hacer las cosas y, en mi opinión, debería evitarse. Hágalo explícitamente: el hecho de que OP tuviera que hacer esta pregunta respalda mi punto.
kratenko
11
¿Puede agregar más detalles, por qué x<2 == false?
Iłya Bursov
15
boolno se convierte en un número entero, boolen Python es un número entero
Antti Haapala
2
Solo para aclarar la declaración de @ AnttiHaapala para cualquier otra persona que venga, bool es una subclase de int.
porglezomp
14

El código original de su pregunta solo funciona en Python 2. Si xes un listen Python 2, la comparación x < yes Falsesi yes un integer. Esto se debe a que no tiene sentido comparar una lista con un número entero. Sin embargo, en Python 2, si los operandos no son comparables, la comparación se basa en CPython en el orden alfabético de los nombres de los tipos ; además, todos los números aparecen primero en las comparaciones de tipo mixto . Esto ni siquiera está detallado en la documentación de CPython 2, y diferentes implementaciones de Python 2 podrían dar resultados diferentes. Eso se [1, 2, 3, 4, 5] < 2evalúa Falseporque 2es un número y, por lo tanto, "más pequeño" que listen CPython. Esta comparación mixta fue finalmentese consideró una característica demasiado oscura y se eliminó en Python 3.0.


Ahora, el resultado de <es a bool; y booles una subclase deint :

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

Básicamente, estás tomando el elemento 0 o 1 dependiendo de si la comparación es verdadera o falsa.


Si prueba el código anterior en Python 3, obtendrá TypeError: unorderable types: list() < int()debido a un cambio en Python 3.0 :

Comparaciones de pedidos

Python 3.0 ha simplificado las reglas para ordenar comparaciones:

Los operadores de comparación de pedidos ( <, <=, >=, >) plantear una TypeErrorexcepción cuando los operandos no tienen un orden natural significativo. Por lo tanto, expresiones como 1 < '', 0 > Noneo len <= lenya no son válidas y, por ejemplo, None < Nonesube en TypeErrorlugar de devolver False. Un corolario es que ordenar una lista heterogénea ya no tiene sentido: todos los elementos deben ser comparables entre sí. Tenga en cuenta que esto no se aplica a los operadores ==y !=: los objetos de diferentes tipos incomparables siempre se comparan de manera desigual entre sí.


Hay muchos tipos de datos que sobrecargan los operadores de comparación para hacer algo diferente (marcos de datos de pandas, matrices de numpy). Si el código que estaba usando hizo algo más, fue porque nox era unlist , sino una instancia de otra clase con el operador <anulado para devolver un valor que no es un bool; y este valor fue manejado especialmente por x[](también conocido como __getitem__/ __setitem__)

Antti Haapala
fuente
6
+FalseHola Perl, hola JavaScript, ¿cómo están?
gato
@cat en Javascript, Perl, convierte el valor como número. En Python es para el UNARY_POSITIVEcódigo de operación que llama al__pos__
Antti Haapala
Creo que quiso decir en __setitem__lugar de __getitem__en su última sección. También espero que no le importe que mi respuesta se haya inspirado en esa parte de su respuesta.
MSeifert
No, quise decir y estaba pensando, __getitem__aunque igualmente podría haber sido __setitem__y__delitem__
Antti Haapala
9

Esto tiene un uso más: código golf. Code golf es el arte de escribir programas que resuelven algún problema en el menor número posible de bytes de código fuente.

return(a,b)[c<d]

es aproximadamente equivalente a

if c < d:
    return b
else:
    return a

excepto que tanto a como b se evalúan en la primera versión, pero no en la segunda versión.

c<devalúa a Trueo False.
(a, b)es una tupla.
Indexar en una tupla funciona como indexar en una lista: (3,5)[1]== 5.
Truees igual 1y Falsees igual a 0.

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

o para False:

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

Hay una buena lista en la red de intercambio de pilas de muchas cosas desagradables que puede hacer con Python para ahorrar unos pocos bytes. /codegolf/54/tips-for-golfing-in-python

Aunque en el código normal, esto nunca debería usarse, y en su caso significaría que xactúa tanto como algo que se puede comparar con un número entero como un contenedor que admite el corte, que es una combinación muy inusual. Probablemente sea código Numpy, como han señalado otros.

Filip Haglund
fuente
6
Code Golf is the art of writing programs: ')
gato
1
Nitpick menor: La bool no se echó a un int, sólo es uno (ver las otras respuestas)
gato
6

En general, podría significar cualquier cosa . Ya era explicó lo que significa que si xes una listo numpy.ndarraypero en general sólo depende de cómo los operadores de comparación ( <, >también cómo, ...) y el / puesta a punto get ( [...]se implementan -Sintaxis).

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 es equivalente a x.__lt__(value)
  • x[value] es (aproximadamente) equivalente a x.__getitem__(value)
  • x[value] = othervaluees (también aproximadamente) equivalente a x.__setitem__(value, othervalue).

Esto se puede personalizar para hacer lo que desee. Solo como ejemplo (imita una indexación numpys-booleana):

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

Así que ahora veamos qué pasa si lo usas:

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

Note que esta es solo una posibilidad. Eres libre de implementar casi todo lo que quieras.

MSeifert
fuente
Yo diría que usar cualquier cosa es realmente demasiado general para un comportamiento lógicamente explicable como la respuesta aceptada.
PascalVKooten
@PascalvKooten ¿No estás de acuerdo con el "cualquier cosa" o con la respuesta generalizada? Creo que es un punto importante a destacar porque la mayoría de los comportamientos lógicos en Python son solo por convención.
MSeifert