Was bedeutet x [x <2] = 0 in Python?

85

Ich bin auf einen Code mit einer ähnlichen Zeile gestoßen

x[x<2]=0

Ich spiele mit Variationen herum und bin immer noch fest davon überzeugt, was diese Syntax bewirkt.

Beispiele:

>>> 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
quelle
7
Es macht nie Sinn, dies mit einer Liste zu tun.
Dbliss
12
Dies ist nur bei NumPy-Arrays oder ähnlichen Objekten sinnvoll, die sich völlig anders verhalten als das Verhalten in Ihren Experimenten oder das in beiden Antworten erläuterte listenbasierte Verhalten.
user2357112 unterstützt Monica
11
Beachten Sie, dass dies in Python 3 nicht funktioniert. Typen können nur verglichen werden, wenn der Vergleich sinnvoll ist. In Python 3 wirft dieses Beispiel TypeError: unorderable types: list() < int().
Morgan Thrapp
2
Zu wenig Informationen. Hätte erwähnen sollen, dass das Array ein Numpy-Array ist.
lmaooooo
3
Ich bin schockiert, dass dies so viele positive Stimmen bekommen hat (obwohl es in der Tat eine gute Frage für das SO-Format ist).
PascalVKooten

Antworten:

119

Dies ist nur bei NumPy- Arrays sinnvoll . Das Verhalten mit Listen ist nutzlos und spezifisch für Python 2 (nicht Python 3). Möglicherweise möchten Sie überprüfen, ob das ursprüngliche Objekt tatsächlich ein NumPy-Array (siehe weiter unten) und keine Liste war.

Aber in Ihrem Code hier ist x eine einfache Liste.

Schon seit

x < 2

ist also falsch, dh 0

x[x<2] ist x[0]

x[0] wird geändert.

Umgekehrt x[x>2]ist x[True]oderx[1]

Also x[1]wird geändert.

Warum passiert das?

Die Vergleichsregeln sind:

  1. Wenn Sie zwei Zeichenfolgen oder zwei numerische Typen bestellen, erfolgt die Reihenfolge wie erwartet (lexikografische Reihenfolge für Zeichenfolge, numerische Reihenfolge für Ganzzahlen).

  2. Wenn Sie einen numerischen und einen nicht numerischen Typ bestellen, steht der numerische Typ an erster Stelle.

  3. Wenn Sie zwei inkompatible Typen bestellen, bei denen keiner numerisch ist, werden sie nach der alphabetischen Reihenfolge ihrer Typnamen sortiert:

Wir haben also die folgende Reihenfolge

numerisch <Liste <Zeichenfolge <Tupel

Siehe die akzeptierte Antwort für Wie vergleicht Python String und Int? .

Wenn x ein NumPy-Array ist , ist die Syntax aufgrund der Booleschen Array-Indizierung sinnvoller . In diesem Fall x < 2ist das überhaupt kein Boolescher Wert. Es ist ein Array von Booleschen Werten, die darstellen, ob jedes Element xkleiner als 2 war. x[x < 2] = 0Anschließend werden die Elemente ausgewählt x, die kleiner als 2 waren, und diese Zellen werden auf 0 gesetzt. Siehe Indizierung .

>>> 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
quelle
11
Angesichts der Tatsache, dass das OP ausdrücklich sagt "Ich bin auf einen Code wie diesen gestoßen ...", halte ich Ihre Antwort, die die numpy-boolesche Indizierung beschreibt, für sehr nützlich. importIch werde mit ziemlicher Sicherheit eine für numpy sehen.
J Richard Snape
2
Sicherlich immer noch eine übermäßig clevere Art, das zu tun? (Im Vergleich zu beispielsweise [0 if i < 2 else i for i in x].) Oder ist dieser Stil in Numpy ermutigt?
Tim Pederick
6
@ TimPederick: Die Verwendung von Listenverständnissen mit NumPy ist eine ziemlich schlechte Idee. Es ist Dutzende bis Hunderte Male langsamer, funktioniert nicht mit Arrays mit beliebigen Dimensionen, es ist einfacher, die Elementtypen zu vermasseln, und es wird eine Liste anstelle eines Arrays erstellt. Die Boolesche Array-Indizierung ist völlig normal und wird in NumPy erwartet.
user2357112 unterstützt Monica
@ TimPederick Zusätzlich zum Leistungseinbruch ist es auch wahrscheinlich, dass derjenige, der den Code geschrieben hat, weiterhin ein numpy-Array verwenden soll. x[x<2]gibt ein numpy-Array zurück, während [0 if i<2 else i for i in x]eine Liste zurückgegeben wird. Dies liegt daran, dass x[x<2]es sich um eine Indizierungsoperation handelt (in numpy / scipy / pandas aufgrund der Fähigkeit, Daten zu maskieren, als Slicing-Operation bezeichnet), während das Listenverständnis eine neue Objektdefinition ist. Siehe NumPy-Indizierung
Michael Delgado
45
>>> x = [1,2,3,4,5]
>>> x<2
False
>>> x[False]
1
>>> x[True]
2

Der Bool wird einfach in eine Ganzzahl konvertiert. Der Index ist entweder 0 oder 1.

Karoly Horvath
quelle
7
Sie könnten darauf hinweisen , dass xund 2sind bestellt konsequent aber willkürlich und dass die Reihenfolge in verschiedenen Python - Implementierungen ändern könnte.
Robᵩ
2
Ich möchte auch hinzufügen, dass dies eine clevere Art ist, Dinge zu tun, und meiner Meinung nach vermieden werden sollte. Tun Sie es explizit - die Tatsache, dass OP diese Frage stellen musste, unterstützt meinen Standpunkt.
Kratenko
11
Können Sie weitere Details hinzufügen, warum x<2 == false?
Iłya Bursov
15
boolwird nicht in eine Ganzzahl konvertiert, eine boolin Python ist eine Ganzzahl
Antti Haapala
2
Nur um die Aussage von @ AnttiHaapala für alle anderen zu klären, die mitkommen, bool ist eine Unterklasse von int.
Porglezomp
14

Der ursprüngliche Code in Ihrer Frage kann nur in Python 2. Wenn xa list2 in Python, der Vergleich x < yist , Falsewenn yein ist integer. Dies liegt daran, dass es nicht sinnvoll ist, eine Liste mit einer Ganzzahl zu vergleichen. Wenn die Operanden in Python 2 nicht vergleichbar sind, basiert der Vergleich in CPython auf der alphabetischen Reihenfolge der Namen der Typen . Außerdem stehen bei gemischten Vergleichen alle Zahlen an erster Stelle . Dies wird in der Dokumentation von CPython 2 nicht einmal beschrieben, und verschiedene Python 2-Implementierungen können zu unterschiedlichen Ergebnissen führen. Das wird [1, 2, 3, 4, 5] < 2ausgewertet, Falseweil 2eine Zahl und damit "kleiner" als eine listin CPython ist. Dieser gemischte Vergleich war schließlichwird als zu undurchsichtig angesehen und in Python 3.0 entfernt.


Das Ergebnis von <ist a bool; und boolist eine Unterklasse vonint :

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

Sie nehmen also im Grunde das Element 0 oder 1, je nachdem, ob der Vergleich wahr oder falsch ist.


Wenn Sie den obigen Code in Python 3 ausprobieren, erhalten Sie TypeError: unorderable types: list() < int()aufgrund einer Änderung in Python 3.0 :

Bestellvergleiche

Python 3.0 hat die Regeln für die Reihenfolge von Vergleichen vereinfacht:

Die Bestellvergleichsoperatoren ( <, <=, >=, >) erhöhen , um eine TypeErrorAusnahme , wenn die Operanden keine sinnvolle natürliche Ordnung haben. So wie Ausdrücke 1 < '', 0 > Noneoder len <= lennicht mehr gültig ist , und zB None < NoneErhöhungen TypeErrorstatt Rückkehr False. Eine Konsequenz ist, dass das Sortieren einer heterogenen Liste keinen Sinn mehr macht - alle Elemente müssen miteinander vergleichbar sein. Beachten Sie, dass dies nicht für die Operatoren ==und gilt !=: Objekte unterschiedlicher unvergleichlicher Typen werden immer ungleich miteinander verglichen.


Es gibt viele Datentypen, die die Vergleichsoperatoren überladen , um etwas anderes zu tun (Datenrahmen von Pandas, Numpys Arrays). Wenn der von Ihnen verwendete Code etwas anderes getan hat, lag dies daran, dass xes sich nicht um einelist Instanz einer anderen Klasse handelte, deren Operator <überschrieben wurde, um einen Wert zurückzugeben, der kein a ist bool. und dieser Wert wurde dann speziell von x[](aka __getitem__/ __setitem__) behandelt

Antti Haapala
quelle
6
+FalseHallo Perl, hey JavaScript, wie geht es euch?
Katze
@cat in Javascript, Perl, konvertiert es den Wert als Zahl. In Python ist es für UNARY_POSITIVEOpcode, der die__pos__
Antti Haapala
Ich denke du meintest __setitem__statt __getitem__in deinem letzten Abschnitt. Ich hoffe auch, dass es Ihnen nichts ausmacht, dass meine Antwort von diesem Teil Ihrer Antwort inspiriert wurde.
MSeifert
Nein, ich meinte und dachte darüber nach, __getitem__obwohl es genauso hätte sein können __setitem__und__delitem__
Antti Haapala
9

Dies hat noch eine weitere Verwendung: Code Golf. Code Golf ist die Kunst, Programme zu schreiben, die ein Problem in möglichst wenigen Quellcode-Bytes lösen.

return(a,b)[c<d]

ist ungefähr gleichbedeutend mit

if c < d:
    return b
else:
    return a

außer dass sowohl a als auch b in der ersten Version ausgewertet werden, nicht jedoch in der zweiten Version.

c<dbewertet zu Trueoder False.
(a, b)ist ein Tupel.
Die Indizierung für ein Tupel funktioniert wie die Indizierung für eine Liste: (3,5)[1]== 5.
Trueist gleich 1und Falseist gleich 0.

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

oder für False:

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

Es gibt eine gute Liste im Stack-Exchange-Netzwerk mit vielen fiesen Dingen, die Sie mit Python tun können, um ein paar Bytes zu sparen. /codegolf/54/tips-for-golfing-in-python

Obwohl dies im normalen Code niemals verwendet werden sollte, würde dies in Ihrem Fall bedeuten, dass es xsowohl als etwas fungiert, das mit einer Ganzzahl verglichen werden kann, als auch als ein Container, der das Schneiden unterstützt, was eine sehr ungewöhnliche Kombination ist. Es ist wahrscheinlich Numpy-Code, wie andere betont haben.

Filip Haglund
quelle
6
Code Golf is the art of writing programs: ')
Katze
1
Minor nitpick: Der Bool ist nicht gegossen in einen int, es einfach ist , ein (siehe die anderen Antworten)
cat
6

Im Allgemeinen könnte es alles bedeuten . Es war bereits erklärt , was es bedeutet , wenn xeine ist listoder numpy.ndarrayaber im Allgemeinen es hängt nur davon ab , wie die Vergleichsoperator ( <, >, ...) und auch , wie das get / set-item ( [...]-Syntax) implementiert.

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!

Weil:

  • x < value ist äquivalent zu x.__lt__(value)
  • x[value] ist (ungefähr) äquivalent zu x.__getitem__(value)
  • x[value] = othervalueist (auch ungefähr) äquivalent zu x.__setitem__(value, othervalue).

Dies kann angepasst werden, um alles zu tun, was Sie wollen. Nur als Beispiel (ahmt eine etwas numpys-boolesche Indizierung nach):

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

Nun wollen wir sehen, was passiert, wenn Sie es verwenden:

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

Beachten Sie, dass dies nur eine Möglichkeit ist. Sie können fast alles implementieren, was Sie wollen.

MSeifert
quelle
Ich würde sagen, alles zu verwenden ist viel zu allgemein für logisch erklärbares Verhalten wie die akzeptierte Antwort.
PascalVKooten
@PascalvKooten Sind Sie mit dem "irgendetwas" oder der allgemeinen Antwort nicht einverstanden? Ich denke, es ist ein wichtiger Punkt, weil das logischste Verhalten in Python nur durch Konvention erfolgt.
MSeifert