Cosa significa x [x <2] = 0 in Python?


85

Mi sono imbattuto in un codice con una riga simile a

x[x<2]=0

Giocando con le variazioni, sono ancora bloccato su ciò che fa questa sintassi.

Esempi:

>>> 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
non ha mai senso farlo con un elenco.
dbliss

12
Questo ha senso solo con array NumPy o oggetti simili, che si comportano in modo completamente diverso dal comportamento nei tuoi esperimenti o dal comportamento basato su elenchi spiegato in entrambe le risposte.
user2357112 supporta Monica

11
Nota, questo non funziona in Python 3. I tipi possono essere confrontati solo quando il confronto ha senso. In Python 3 questo esempio genera TypeError: unorderable types: list() < int().
Morgan Thrapp

2
Troppe poche informazioni. Avrei dovuto dire che l'array è un array numpy.
lmaooooo

3
Sono scioccato che questo abbia ricevuto così tanti voti positivi (anche se è davvero una buona domanda per il formato SO).
PascalVKooten

Risposte:


120

Questo ha senso solo con gli array NumPy . Il comportamento con le liste è inutile e specifico per Python 2 (non Python 3). Potresti voler ricontrollare se l'oggetto originale era effettivamente un array NumPy (vedi più avanti) e non un elenco.

Ma nel tuo codice qui, x è un semplice elenco.

Da

x < 2

è False cioè 0, quindi

x[x<2] è x[0]

x[0] viene cambiato.

Al contrario, x[x>2]è x[True]ox[1]

Quindi, x[1]viene cambiato.

Perché succede questo?

Le regole per il confronto sono:

  1. Quando si ordinano due stringhe o due tipi numerici, l'ordinamento viene eseguito nel modo previsto (ordinamento lessicografico per stringa, ordinamento numerico per interi).

  2. Quando ordini un tipo numerico e uno non numerico, il tipo numerico viene prima.

  3. Quando si ordinano due tipi incompatibili, dove nessuno dei due è numerico, vengono ordinati in base all'ordine alfabetico dei nomi dei tipi:

Quindi, abbiamo il seguente ordine

numeric <list <string <tuple

Vedi la risposta accettata per Come fa Python a confrontare string e int? .

Se x è un array NumPy , la sintassi ha più senso a causa dell'indicizzazione dell'array booleano . In tal caso, x < 2non è affatto un booleano; è un array di valori booleani che rappresentano se ogni elemento di xera minore di 2. x[x < 2] = 0quindi seleziona gli elementi di xche erano minori di 2 e imposta quelle celle a 0. Vedi Indicizzazione .

>>> 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
Dato che l'OP dice specificamente "Mi sono imbattuto in un codice come questo ...", penso che la tua risposta che descrive l'indicizzazione booleana numpy sia molto utile - potrebbe valere la pena sottolineare che se l'OP scorre il codice che hanno guardato, loro ' Quasi sicuramente vedrò un importper numpy.
J Richard Snape

2
Ancora un modo eccessivamente intelligente per farlo, sicuramente? (Rispetto a, diciamo [0 if i < 2 else i for i in x],.) O questo stile è incoraggiato in Numpy?
Tim Pederick

6
@TimPederick: Usare le liste di comprensione con NumPy è una pessima idea. È da decine a centinaia di volte più lento, non funziona con array di dimensioni arbitrarie, è più facile rovinare i tipi di elementi e crea un elenco invece di un array. L'indicizzazione degli array booleani è completamente normale e prevista in NumPy.
user2357112 supporta Monica

@TimPederick Oltre al calo delle prestazioni, è anche probabile che chiunque abbia scritto il codice abbia intenzione di continuare a utilizzare un array numpy. x[x<2]restituirà un array numpy, mentre [0 if i<2 else i for i in x]restituirà un elenco. Questo perché x[x<2]è un'operazione di indicizzazione (indicata in numpy / scipy / pandas come operazione di affettamento a causa della capacità di mascherare i dati), mentre la comprensione dell'elenco è una nuova definizione di oggetto. Vedi l' indicizzazione NumPy
Michael Delgado

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

Il valore bool viene semplicemente convertito in un numero intero. L'indice è 0 o 1.


7
Potresti menzionarlo xe 2sono " ordinati in modo coerente ma arbitrario " e che l'ordine potrebbe cambiare in diverse implementazioni di Python.
Robᵩ

2
Aggiungo anche che questo è un modo intelligente di fare le cose e dovrebbe a mio avviso essere evitato. Fallo esplicitamente: il fatto che OP abbia dovuto porre questa domanda supporta il mio punto.
kratenko

11
puoi aggiungere più dettagli, perché x<2 == false?
Iłya Bursov

15
boolnon viene convertito in un numero intero, un boolin Python è un numero intero
Antti Haapala

2
Solo per chiarire l'affermazione di @ AnttiHaapala per chiunque altro si presenti, bool è una sottoclasse di int.
porglezomp

14

Il codice originale nella tua domanda funziona solo in Python 2. Se xè a listin Python 2, il confronto x < yè Falsese yè un integer. Questo perché non ha senso confrontare un elenco con un numero intero. Tuttavia in Python 2, se gli operandi non sono confrontabili, il confronto si basa in CPython sull'ordinamento alfabetico dei nomi dei tipi ; inoltre, tutti i numeri vengono prima nei confronti di tipo misto . Questo non è nemmeno spiegato nella documentazione di CPython 2 e diverse implementazioni di Python 2 potrebbero dare risultati diversi. Vale [1, 2, 3, 4, 5] < 2a Falseperché 2è un numero e quindi "più piccolo" di a listin CPython. Alla fine questo confronto misto è statoritenuta una funzionalità troppo oscura ed è stata rimossa in Python 3.0.


Ora, il risultato di <è a bool; ed boolè una sottoclasse diint :

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

Quindi in pratica stai prendendo l'elemento 0 o 1 a seconda che il confronto sia vero o falso.


Se provi il codice sopra in Python 3, otterrai a TypeError: unorderable types: list() < int()causa di una modifica in Python 3.0 :

Ordinazione di confronti

Python 3.0 ha semplificato le regole per ordinare i confronti:

Gli operatori di confronto ordinamento ( <, <=, >=, >) sollevare un TypeErroreccezione quando gli operandi non hanno un ordinamento naturale significativo. Pertanto, espressioni come 1 < '', 0 > Noneo len <= lennon sono più valide, e ad esempio None < Nonesolleva TypeErrorinvece di restituire False. Un corollario è che l'ordinamento di un elenco eterogeneo non ha più senso: tutti gli elementi devono essere confrontabili tra loro. Si noti che questo non si applica agli operatori ==e !=: oggetti di diversi tipi incomparabili vengono sempre confrontati in modo diverso tra loro.


Ci sono molti tipi di dati che sovraccaricano gli operatori di confronto per fare qualcosa di diverso (dataframe dai panda, array di numpy). Se il codice che stavi usando faceva qualcos'altro, era perché nonx era alist , ma un'istanza di qualche altra classe con l'operatore <sovrascritto per restituire un valore che non è a bool; e questo valore è stato quindi gestito appositamente da x[](aka __getitem__/ __setitem__)


6
+FalseCiao Perl, ehi JavaScript, come va?
gatto

@cat in Javascript, Perl, converte il valore come numero. In Python è per il UNARY_POSITIVEcodice operativo che chiama__pos__
Antti Haapala

Penso che intendevi __setitem__invece che __getitem__nella tua ultima sezione. Inoltre spero che non ti dispiaccia che la mia risposta sia stata ispirata da quella parte della tua risposta.
MSeifert

No, volevo dire e stavo pensando __getitem__anche se ugualmente avrebbe potuto essere __setitem__e__delitem__
Antti Haapala

9

Questo ha un altro uso: il golf di codice. Il code golf è l'arte di scrivere programmi che risolvono alcuni problemi con il minor numero possibile di byte di codice sorgente.

return(a,b)[c<d]

è più o meno equivalente a

if c < d:
    return b
else:
    return a

tranne che sia a che b sono valutati nella prima versione, ma non nella seconda versione.

c<drestituisce Trueo False.
(a, b)è una tupla.
L'indicizzazione su una tupla funziona come l'indicizzazione su una lista: (3,5)[1]== 5.
Trueè uguale a 1ed Falseè uguale a 0.

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

o per False:

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

C'è un buon elenco sulla rete di scambio di stack di molte cose brutte che puoi fare a Python per salvare alcuni byte. /codegolf/54/tips-for-golfing-in-python

Sebbene nel codice normale questo non dovrebbe mai essere usato, e nel tuo caso significherebbe che xagisce sia come qualcosa che può essere paragonato a un numero intero che come un contenitore che supporta lo slicing, che è una combinazione molto insolita. Probabilmente è codice Numpy, come altri hanno sottolineato.


6
Code Golf is the art of writing programs: ')
gatto

1
Nitpick Minore: Il bool non è gettato a int, semplicemente è uno (vedere le altre risposte)
cat

6

In generale potrebbe significare qualsiasi cosa . E 'stato già spiegato che cosa significa se xè un listo numpy.ndarray, ma, in generale, dipende solo da come gli operatori di confronto ( <, >, ...) e anche come il get / set-item ( [...]sono implementati -syntax).

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!

Perché:

  • x < value è equivalente a x.__lt__(value)
  • x[value] è (approssimativamente) equivalente a x.__getitem__(value)
  • x[value] = othervalueè (anche approssimativamente) equivalente a x.__setitem__(value, othervalue).

Questo può essere personalizzato per fare tutto ciò che desideri. Proprio come un esempio (imita un po 'di indicizzazione 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

Quindi ora vediamo cosa succede se lo usi:

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

Notare che questa è solo una possibilità. Sei libero di implementare quasi tutto ciò che desideri.


Direi che usare qualsiasi cosa è davvero troppo generico per un comportamento spiegabile logicamente come la risposta accettata.
PascalVKooten

@PascalvKooten Non sei d'accordo con il "qualcosa" o con la risposta generalizzata? Penso che sia un punto importante da sottolineare perché la maggior parte del comportamento logico in Python è solo per convenzione.
MSeifert
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.