2016-04-13 27 views
83

mi sono imbattuto in un codice, con una linea simile aCosa significa x [x <2] = 0 in Python?

x[x<2]=0 

Giocando intorno con variazioni, sto ancora bloccato su ciò che questa sintassi fa.

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 una lista. – dbliss

+12

Questo ha senso solo con gli array NumPy o oggetti simili, che si comportano in modo completamente diverso dal comportamento negli esperimenti o dal comportamento basato su elenchi spiegato in entrambe le risposte. – user2357112

+11

Nota, questo non funziona in Python 3. I tipi possono essere confrontati solo quando il confronto ha senso. In Python 3 questo esempio lancia 'TypeError: types non ordinabili: list()

risposta

115

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

Ma nel tuo codice qui, x è una semplice lista.

Dal

x < 2 

è falso cioè 0, quindi

x[x<2] è x[0]

x[0] viene cambiato.

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

Quindi, x[1] ottiene cambiati.

Perché ciò accade?

Le regole per il confronto sono:

  1. Quando si ordina due stringhe o due tipi numerici ordinamento è fatto nel modo previsto (ordinamento lessicografico per le stringhe, ordinamento numerico per numeri interi).

  2. Quando si ordina un tipo numerico e non numerico, il tipo numerico viene prima.

  3. Quando si ordina due tipi incompatibili in cui nessuno dei due è numerico, che sono ordinate per l'ordine alfabetico dei loro nomi dei tipi:

Così, abbiamo il seguente ordine

numerico < lista < stringa < tupla

Vedere la risposta accettata per How does Python compare string and int?.

Se x è un array NumPy, quindi la sintassi più senso perché di matrice booleana indicizzazione.In tal caso, x < 2 non è affatto un valore booleano; è una matrice di booleani che rappresentano se ciascun elemento di è inferiore a 2. x[x < 2] = 0 quindi seleziona gli elementi di x inferiori a 2 e imposta tali celle su 0. Vedere 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

Dato che l'OP dice esplicitamente "Mi sono imbattuto in un codice come questo ...", penso che la tua risposta che descrive l'indicizzazione booleana numerica sia molto utile - potrebbe valere la pena sottolineare che se l'OP scorre il codice che hanno visto, quasi sicuramente vedremo un "import" per numpy. –

+2

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

+6

@TimPederick: l'uso di list comprehensions con NumPy è una pessima idea. È dozzina a centinaia di volte più lento, non funziona con gli array a dimensione arbitraria, è più facile ottenere i tipi di elementi rovinati e crea una lista invece di una matrice. L'indicizzazione degli array booleani è completamente normale e prevista in NumPy. – user2357112

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

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

+7

Si potrebbe dire che 'x' e' 2' sono ["* ordinati coerentemente ma arbitrariamente *"] (https://docs.python.org/2/library/stdtypes.html#comparisons) e che l'ordine potrebbe cambiare in diverse implementazioni Python. –

+0

Yepp. python3: http://ideone.com/1Cw4gb e http://stackoverflow.com/questions/3270680/how-does-python-compare-string-and-int –

+2

Vorrei anche aggiungere che questo è un modo * intelligente * di fare le cose e dovrebbe, a mio parere, essere evitato. Fallo esplicitamente - il fatto che OP abbia dovuto porre questa domanda conferma il mio punto di vista. – kratenko

14

Il codice originale nella tua domanda funziona solo in Python 2. Se x è un list in Python 2, il confronto è x < yFalse se y è un int Eger. Questo perché non ha senso confrontare una lista con un numero intero. Tuttavia in Python 2, se gli operandi non sono confrontabili, il confronto è basato su CPython su alphabetical ordering of the names of the types; inoltre tutti i numeri vengono per primi in confronti di tipo misto. Questo non è nemmeno spiegato nella documentazione di CPython 2, e diverse implementazioni di Python 2 potrebbero dare risultati diversi. Quello è [1, 2, 3, 4, 5] < 2 valuta a False perché 2 è un numero e quindi "più piccolo" di uno list in CPython. Questo confronto misto è stato alla fine deemed to be too obscure a feature ed è stato rimosso in Python 3.0.


Ora, il risultato di < è un bool; e bool is a subclass of int:

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

Quindi, in pratica si sta prendendo l'elemento 0 o 1 a seconda se il confronto è vero o falso.


caso si tenta il codice di cui sopra in Python 3, si otterrà TypeError: unorderable types: list() < int() causa a change in Python 3.0:

Ordering Comparisons

Python 3.0 has simplified the rules for ordering comparisons:

The ordering comparison operators (< , <= , >= , >) raise a TypeError exception when the operands don’t have a meaningful natural ordering. Thus, expressions like 1 < '' , 0 > None or len <= len are no longer valid, and e.g. None < None raises TypeError instead of returning False . A corollary is that sorting a heterogeneous list no longer makes sense – all the elements must be comparable to each other. Note that this does not apply to the == and != operators: objects of different incomparable types always compare unequal to each other.


Ci sono molte tipi di dati che sovraccarico i operatori di confronto per fare qualcosa diverso (dataframes da panda, array di numpy). Se il codice che stavi utilizzando ha fatto qualcos'altro, era xnon uno list, ma un'istanza di qualche altra classe con operatore < ha sostituito per restituire un valore che non è un bool; e questo valore è stato quindi gestito appositamente da x[] (alias __getitem__/__setitem__)

+6

'+ False' Ciao Perl, hey JavaScript, come va? – cat

+0

@cat in Javascript, Perl, converte il valore come numero. In Python è per l'opcode 'UNARY_POSITIVE' che chiama il' __pos__' –

+0

@Anti che ha lo stesso risultato funky, da qui il mio commento: P – cat

9

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

return(a,b)[c<d] 

è approssimativamente equivalente a

if c < d: 
    return b 
else: 
    return a 

salvo che sia a che b vengono valutati nella prima versione, ma non nella seconda versione.

c<d restituisce True o False.
(a, b) è una tupla.
L'indicizzazione su una tupla funziona come l'indicizzazione in un elenco: (3,5)[1] == 5.
True è uguale a 1 e 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'è una buona lista sulla rete di scambio pila di molte cose brutte che puoi fare per python al fine di risparmiare pochi byte. https://codegolf.stackexchange.com/questions/54/tips-for-golfing-in-python

Anche se in codice normale questo non dovrebbe essere utilizzato, e nel tuo caso vorrebbe dire che x agisce sia come qualcosa che può essere paragonato a un intero e come un contenitore che supporta affettare, che è una combinazione molto insolita. È probabilmente il codice di Numpy, come altri hanno sottolineato.

+6

' Code Golf è l'arte di scrivere programmi': ') – cat

+1

Minor nitpick: Il bool non è * cast * to un int, è solo * è * uno (vedi le altre risposte) – cat

+0

Buon punto, ho perso che True/False sono interi :) –

6

In generale potrebbe significare qualsiasi cosa. È stato già spiegato cosa significa se è un list o ma in generale dipende solo da come gli operatori di confronto (<, >, ...) e anche come viene implementato l'elemento get/set ([...] -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! 

Poiché:

  • 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 qualsiasi cosa che vuoi. A titolo di 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 

Così ora vediamo cosa succede se lo si utilizza:

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

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

+0

Direi usando ** qualsiasi cosa ** è davvero troppo generico per un comportamento logicamente spiegabile come il risposta accettata – PascalVKooten

+0

@PascalvKooten Non sei d'accordo con il "nulla" o con la risposta generalizzata? Penso che sia un punto importante da fare perché la maggior parte dei comportamenti logici in python è solo per convenzione. – MSeifert