Que signifie x [x <2] = 0 en Python?

85

Je suis tombé sur du code avec une ligne similaire à

x[x<2]=0

Jouant avec les variations, je suis toujours coincé sur ce que fait cette syntaxe.

Exemples:

>>> 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
la source
7
cela n'a jamais de sens de faire cela avec une liste.
dbliss
12
Cela n'a de sens qu'avec des tableaux NumPy ou des objets similaires, qui se comportent complètement différemment du comportement de vos expériences ou du comportement basé sur une liste expliqué dans l'une ou l'autre des réponses.
user2357112 prend en charge Monica le
11
Notez que cela ne fonctionne pas en Python 3. Les types ne peuvent être comparés que lorsque la comparaison a du sens. Dans Python 3, cet exemple lance TypeError: unorderable types: list() < int().
Morgan Thrapp
2
Trop peu d'informations. J'aurais dû mentionner que le tableau est un tableau numpy.
lmaooooo
3
Je suis choqué que cela ait eu autant de votes positifs (même si c'est en effet une bonne question pour le format SO).
PascalVKooten

Réponses:

120

Cela n'a de sens qu'avec les tableaux NumPy . Le comportement avec les listes est inutile, et spécifique à Python 2 (pas à Python 3). Vous voudrez peut-être vérifier si l'objet d'origine était bien un tableau NumPy (voir plus loin ci-dessous) et non une liste.

Mais dans votre code ici, x est une simple liste.

Depuis

x < 2

est faux c'est-à-dire 0, donc

x[x<2] est x[0]

x[0] se change.

Inversement, x[x>2]est x[True]oux[1]

Alors, ça x[1]change.

Pourquoi cela arrive-t-il?

Les règles de comparaison sont:

  1. Lorsque vous commandez deux chaînes ou deux types numériques, l'ordre est effectué de la manière attendue (ordre lexicographique pour chaîne, ordre numérique pour les entiers).

  2. Lorsque vous commandez un type numérique et un type non numérique, le type numérique vient en premier.

  3. Lorsque vous commandez deux types incompatibles où aucun n'est numérique, ils sont classés par ordre alphabétique de leurs noms de types:

Donc, nous avons l'ordre suivant

numérique <liste <chaîne <tuple

Voir la réponse acceptée pour Comment Python compare-t-il string et int? .

Si x est un tableau NumPy , la syntaxe a plus de sens en raison de l' indexation de tableau booléen . Dans ce cas, ce x < 2n'est pas du tout un booléen; c'est un tableau de booléens représentant si chaque élément de xétait inférieur à 2. x[x < 2] = 0puis sélectionne les éléments de xqui étaient inférieurs à 2 et définit ces cellules sur 0. Voir Indexation .

>>> 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
la source
11
Étant donné que l'OP dit spécifiquement "Je suis tombé sur un code comme celui-ci ...", je pense que votre réponse décrivant l'indexation booléenne numpy est très utile - pourrait valoir la peine de souligner que si l'OP fait défiler le code qu'ils ont regardé, ils ' ll verra certainement un importpour numpy.
J Richard Snape
2
Encore une façon trop intelligente de le faire, sûrement? (Par rapport à, disons [0 if i < 2 else i for i in x],.) Ou est-ce que ce style est encouragé dans Numpy?
Tim Pederick le
6
@TimPederick: L'utilisation de la compréhension de liste avec NumPy est une très mauvaise idée. C'est des dizaines à des centaines de fois plus lent, cela ne fonctionne pas avec des tableaux de dimensions arbitraires, il est plus facile de bousiller les types d'élément et cela crée une liste au lieu d'un tableau. L'indexation des tableaux booléens est tout à fait normale et attendue dans NumPy.
user2357112 prend en charge Monica le
@TimPederick En plus de l'atteinte des performances, il est également probable que celui qui a écrit le code ait l'intention de continuer à utiliser un tableau numpy. x[x<2]renverra un tableau numpy, tandis que [0 if i<2 else i for i in x]renvoie une liste. Cela est dû au fait qu'il x[x<2]s'agit d'une opération d'indexation (appelée dans numpy / scipy / pandas une opération de découpage en raison de la possibilité de masquer les données), alors que la compréhension de la liste est une nouvelle définition d'objet. Voir l' indexation NumPy
Michael Delgado
45
>>> x = [1,2,3,4,5]
>>> x<2
False
>>> x[False]
1
>>> x[True]
2

Le booléen est simplement converti en entier. L'index est 0 ou 1.

Karoly Horvath
la source
7
Vous pourriez mentionner cela xet 2sont " ordonnés de manière cohérente mais arbitraire " et que l'ordre peut changer dans différentes implémentations Python.
Robᵩ
2
J'ajouterais également que c'est une manière intelligente de faire les choses et que je pense qu'il faut l'éviter. Faites-le explicitement - le fait qu'OP ait dû poser cette question appuie mon argument.
kratenko
11
pouvez-vous ajouter plus de détails, pourquoi x<2 == false?
Iłya Bursov
15
booln'est pas converti en un entier, a boolen Python est un entier
Antti Haapala
2
Juste pour clarifier la déclaration de @ AnttiHaapala pour quiconque se présente, bool est une sous-classe de int.
porglezomp
14

Le code d'origine de votre question ne fonctionne qu'en Python 2. Si xest un listen Python 2, la comparaison x < yest Falsesi yest un integer. En effet, il n’est pas logique de comparer une liste avec un entier. Cependant en Python 2, si les opérandes ne sont pas comparables, la comparaison est basée en CPython sur l' ordre alphabétique des noms des types ; en outre, tous les chiffres viennent en premier dans les comparaisons de type mixte . Cela n'est même pas précisé dans la documentation de CPython 2, et différentes implémentations de Python 2 pourraient donner des résultats différents. Cela [1, 2, 3, 4, 5] < 2équivaut à Falseparce que 2c'est un nombre et donc "plus petit" qu'un listdans CPython. Cette comparaison mitigée a finalement étéconsidérée comme une fonctionnalité trop obscure , et a été supprimée dans Python 3.0.


Maintenant, le résultat de <est un bool; et boolest une sous - classe deint :

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

Donc, fondamentalement, vous prenez l'élément 0 ou 1 selon que la comparaison est vraie ou fausse.


Si vous essayez le code ci-dessus dans Python 3, vous obtiendrez en TypeError: unorderable types: list() < int()raison d' un changement dans Python 3.0 :

Comparaisons de commandes

Python 3.0 a simplifié les règles de classement des comparaisons:

Les opérateurs de comparaison de commande ( <, <=, >=, >) soulever une TypeErrorexception lorsque les opérandes ne disposent pas d' un véritable ordre naturel. Ainsi, les expressions comme 1 < '', 0 > Noneou len <= lenne sont plus valides, et par exemple, None < Noneaugmente TypeErrorau lieu de retourner False. Un corollaire est que le tri d'une liste hétérogène n'a plus de sens - tous les éléments doivent être comparables les uns aux autres. Notez que cela ne s'applique pas aux opérateurs ==et !=: les objets de différents types incomparables se comparent toujours inégaux les uns aux autres.


Il existe de nombreux types de données qui surchargent les opérateurs de comparaison pour faire quelque chose de différent (dataframes de pandas, tableaux de numpy). Si le code que vous utilisiez faisait autre chose, c'était parce qu'il nex s'agissait pas d'unlist , mais d'une instance d'une autre classe avec un opérateur <remplacé pour renvoyer une valeur qui n'est pas a bool; et cette valeur a ensuite été gérée spécialement par x[](aka __getitem__/ __setitem__)

Antti Haapala
la source
6
+FalseSalut Perl, hey JavaScript, comment vas-tu?
chat du
@cat en Javascript, Perl, il convertit la valeur en nombre. En Python, c'est pour l' UNARY_POSITIVEopcode qui appelle le__pos__
Antti Haapala
Je pense que vous vouliez dire __setitem__plutôt que __getitem__dans votre dernière section. J'espère également que cela ne vous dérange pas que ma réponse ait été inspirée par cette partie de votre réponse.
MSeifert
Non, je voulais dire et je pensais __getitem__bien que tout aussi aurait pu être __setitem__et__delitem__
Antti Haapala
9

Cela a une autre utilité: le code golf. Le code golf est l'art d'écrire des programmes qui résolvent certains problèmes en aussi peu d'octets de code source que possible.

return(a,b)[c<d]

équivaut à peu près à

if c < d:
    return b
else:
    return a

sauf que a et b sont évalués dans la première version, mais pas dans la deuxième version.

c<dévalue à Trueou False.
(a, b)est un tuple.
L'indexation sur un tuple fonctionne comme l'indexation sur une liste: (3,5)[1]== 5.
Trueest égal à 1et Falseest égal à 0.

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

ou pour False:

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

Il existe une bonne liste sur le réseau d'échange de piles de nombreuses choses désagréables que vous pouvez faire à python afin d'économiser quelques octets. /codegolf/54/tips-for-golfing-in-python

Bien que dans le code normal, cela ne devrait jamais être utilisé, et dans votre cas, cela signifierait qu'il xagit à la fois comme quelque chose qui peut être comparé à un entier et comme un conteneur qui prend en charge le découpage, ce qui est une combinaison très inhabituelle. C'est probablement du code Numpy, comme d'autres l'ont souligné.

Filip Haglund
la source
6
Code Golf is the art of writing programs: ')
chat le
1
Petit nitpick: le booléen n'est pas lancé un int, juste est un (voir les autres réponses)
cat
6

En général, cela peut vouloir dire n'importe quoi . Il était déjà expliqué ce qu'il signifie que si xest un listou numpy.ndarraymais en général , il ne dépend que de la façon dont les opérateurs de comparaison ( <, >...) et aussi comment le get / set-point ( [...]-syntax) sont mis en œuvre.

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!

Car:

  • x < value est équivalent à x.__lt__(value)
  • x[value] équivaut (à peu près) à x.__getitem__(value)
  • x[value] = othervalueest (également à peu près) équivalent à x.__setitem__(value, othervalue).

Cela peut être personnalisé pour faire tout ce que vous voulez. Juste à titre d'exemple (imite un peu l'indexation numpys-booléenne):

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

Voyons maintenant ce qui se passe si vous l'utilisez:

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

Notez que ce n'est qu'une possibilité. Vous êtes libre de mettre en œuvre presque tout ce que vous voulez.

MSeifert
la source
Je dirais que l'utilisation de quoi que ce soit est vraiment trop générale pour un comportement logiquement explicable comme la réponse acceptée.
PascalVKooten
@PascalvKooten Êtes-vous en désaccord avec le "quelque chose" ou avec la réponse généralisée? Je pense que c'est un point important à souligner car la plupart des comportements logiques en python sont juste par convention.
MSeifert