Je suis tombé sur du code avec une ligne similaire à
x[x<2]=0
En jouant avec les variations, je suis toujours bloqué 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]
Cela n'a de sens qu'avec NumPy arrays . 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 ci-dessous) et non une liste.
Mais dans votre code ici, x est une simple liste.
Puisque
x < 2
est False i.e 0, donc
x[x<2]
est x[0]
x[0]
est changé.
Inversement, x[x>2]
est x[True]
ou x[1]
Alors, x[1]
est changé.
Pourquoi cela se produit-il?
Les règles de comparaison sont les suivantes:
Lorsque vous commandez deux chaînes ou deux types numériques, l'ordre est effectué de la manière attendue (ordre lexicographique pour une chaîne, ordre numérique pour les entiers).
Lorsque vous commandez un type numérique et un type non numérique, le type numérique vient en premier.
Lorsque vous commandez deux types incompatibles où aucun des deux n'est numérique, ils sont classés par ordre alphabétique de leurs noms:
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 est plus logique en raison de indexation de tableau booléen . Dans ce cas, x < 2
n'est pas un booléen du tout; c'est un tableau de booléens indiquant si chaque élément de x
est inférieur à 2. x[x < 2] = 0
sélectionne ensuite les éléments de x
qui é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
>>> x = [1,2,3,4,5]
>>> x<2
False
>>> x[False]
1
>>> x[True]
2
Le booléen est simplement converti en un entier. L'indice est 0 ou 1.
Le code original de votre question ne fonctionne que dans Python 2. Si x
est un list
dans Python 2, la comparaison x < y
est False
si y
est un int
eger. En effet, il n’a aucun sens de comparer une liste à un entier. Cependant, dans Python 2, si les opérandes ne sont pas comparables, la comparaison est basée dans CPython sur l'ordre alphabétique des noms des types ; En outre , tous les nombres apparaissent en premier dans les comparaisons de type mixte . Ceci 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. C'est-à-dire que [1, 2, 3, 4, 5] < 2
Est évalué à False
car 2
Est un nombre et est donc "plus petit" qu'un list
dans CPython. Cette comparaison mixte a finalement été jugée trop obscure et a été supprimée dans Python 3.0.
Maintenant, le résultat de <
Est un bool
; et bool
est un sous-classe sur int
:
>>> isinstance(False, int)
True
>>> isinstance(True, int)
True
>>> False == 0
True
>>> True == 1
True
>>> False + 5
5
>>> True + 5
6
Donc, en gros, 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 TypeError: unorderable types: list() < int()
en raison de modification de Python 3. :
Comparaisons de commandes
Python 3.0 a simplifié les règles pour les comparaisons de commandes:
Les opérateurs de comparaison de commande (
<
,<=
,>=
,>
) Déclenchent une exceptionTypeError
lorsque les opérandes ne possèdent pas ordre naturel significatif. Ainsi, des expressions telles que1 < ''
,0 > None
Oulen <= len
Ne sont plus valides, et par exempleNone < None
SoulèveTypeError
au lieu de retournerFalse
. 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 types incomparables différents se comparent toujours de manière inégale.
Il existe de nombreux types de données qui surcharge les opérateurs de comparaison doivent faire quelque chose différent (images de données issues de pandas, de tableaux de numpy). Si le code que vous utilisiez faisait autre chose, c'était parce que x
était pas un list
, mais une instance d'une autre classe avec l'opérateur <
Remplacé pour renvoyer une valeur qui n'est pas un bool
; et cette valeur a ensuite été gérée spécialement par x[]
(alias __getitem__
/__setitem__
)
Cela a une autre utilisation: le code golf. Le code golf est l’art d’écrire des programmes qui résolvent un problème avec le moins d’octets de code source possible.
return(a,b)[c<d]
est à peu près équivalent à
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
est évalué à True
ou False
.(a, b)
est un tuple.
L’indexation sur un tuple fonctionne comme l’indexation sur une liste: (3,5)[1]
== 5
.True
est égal à 1
et False
est égal à 0
.
(a,b)[c<d]
(a,b)[True]
(a,b)[1]
b
ou pour False
:
(a,b)[c<d]
(a,b)[False]
(a,b)[0]
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 de sauvegarder quelques octets. https://codegolf.stackexchange.com/questions/54/astuces pour jouer au golf en python
Bien qu'en code normal, cela ne devrait jamais être utilisé, et dans votre cas, cela voudrait dire que x
agit à la fois comme un élément pouvant être comparé à un entier et comme un conteneur prenant en charge le découpage en tranches, une combinaison très inhabituelle. C'est probablement du code Numpy, comme d'autres l'ont souligné.
En général, cela pourrait signifier n'importe quoi. Nous avons déjà expliqué ce que cela signifie si x
est un list
ou numpy.ndarray
, Mais en général cela dépend uniquement de la façon dont les opérateurs de comparaison (<
, >
, ...) et comment l’objet get/set-item ([...]
- syntaxe) est implémenté.
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]
Est (approximativement) équivalent à x.__getitem__(value)
x[value] = othervalue
Est (aussi approximativement) équivalent à x.__setitem__(value, othervalue)
.Cela peut être personnalisé pour faire n'importe quoi vous voulez. Juste à titre d'exemple (imite un peu l'indexation 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
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 ceci est juste une possibilité. Vous êtes libre de mettre en œuvre presque tout ce que vous voulez.