Co oznacza x [x <2] = 0 w Pythonie?

85

Natknąłem się na kod z linią podobną do

x[x<2]=0

Bawiąc się wariacjami, wciąż tkwię w tym, co robi ta składnia.

Przykłady:

>>> 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
źródło
7
nigdy nie ma sensu robić tego z listą.
dbliss
12
Ma to sens tylko w przypadku tablic NumPy lub podobnych obiektów, które zachowują się zupełnie inaczej niż zachowanie w twoich eksperymentach lub zachowanie oparte na liście wyjaśnione w obu odpowiedziach.
user2357112 obsługuje Monikę
11
Uwaga, to nie działa w Pythonie 3. Typy można porównywać tylko wtedy, gdy ma to sens. W Pythonie 3 ten przykład rzuca TypeError: unorderable types: list() < int().
Morgan Thrapp
2
Za mało informacji. Powinienem był wspomnieć, że tablica jest tablicą numpy.
lmaooooo
3
Jestem zszokowany, że otrzymałem tak wiele głosów pozytywnych (chociaż jest to rzeczywiście dobre pytanie dla formatu SO).
PascalVKooten

Odpowiedzi:

120

Ma to sens tylko w przypadku tablic NumPy . Zachowanie z listami jest bezużyteczne i specyficzne dla Pythona 2 (nie Pythona 3). Możesz chcieć dwukrotnie sprawdzić, czy oryginalny obiekt rzeczywiście był tablicą NumPy (patrz poniżej), a nie listą.

Ale w twoim kodzie tutaj x jest prostą listą.

Od

x < 2

jest fałszywe, czyli 0, dlatego

x[x<2] jest x[0]

x[0] zmienia się.

Odwrotnie, x[x>2]jest x[True]lubx[1]

Więc x[1]zmienia się.

Dlaczego to się dzieje?

Zasady porównania to:

  1. Kiedy zamawiasz dwa ciągi lub dwa typy liczbowe, porządkowanie odbywa się w oczekiwany sposób (porządek leksykograficzny dla łańcuchów, porządek numeryczny dla liczb całkowitych).

  2. Kiedy zamawiasz typ numeryczny i nieliczbowy, typ numeryczny jest pierwszy.

  3. Kiedy zamawiasz dwa niezgodne typy, w których żaden nie jest numeryczny, są one sortowane według kolejności alfabetycznej ich nazw typów:

Mamy więc następującą kolejność

numeryczne <lista <ciąg <krotka

Zobacz zaakceptowaną odpowiedź na pytanie Jak Python porównuje ciąg i int? .

Jeśli x jest tablicą NumPy , składnia ma większy sens ze względu na indeksowanie tablic logicznych . W takim przypadku x < 2nie jest w ogóle wartością logiczną; jest to tablica wartości logicznych reprezentujących, czy każdy element xbył mniejszy niż 2., x[x < 2] = 0a następnie wybiera elementy, xktórych były mniejsze niż 2 i ustawia te komórki na 0. Zobacz Indeksowanie .

>>> 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
źródło
11
Biorąc pod uwagę, że OP wyraźnie mówi: „Natknąłem się na taki kod ...”, myślę, że twoja odpowiedź opisująca indeksowanie numpy boolean jest bardzo przydatna - warto zauważyć, że jeśli OP przewija kod, na który patrzyli, Prawie na pewno zobaczę importodrętwienie.
J Richard Snape
2
Z pewnością nadal jest to zbyt sprytny sposób? (W porównaniu, powiedzmy [0 if i < 2 else i for i in x],.) A może jest to zachęcający styl w Numpy?
Tim Pederick,
6
@TimPederick: Używanie wyrażeń listowych w NumPy to całkiem zły pomysł. Jest dziesiątki do setek razy wolniejszy, nie działa z tablicami o dowolnych wymiarach, łatwiej jest zepsuć typy elementów i tworzy listę zamiast tablicy. Indeksowanie tablic logicznych jest całkowicie normalne i oczekiwane w NumPy.
user2357112 obsługuje Monikę
@TimPederick Oprócz uderzenia w wydajność jest również prawdopodobne, że ktokolwiek napisał kod, zamierzał nadal używać tablicy numpy. x[x<2]zwróci tablicę numpy, podczas gdy [0 if i<2 else i for i in x]zwróci listę. Dzieje się tak, ponieważ x[x<2]jest to operacja indeksowania (określana w numpy / scipy / pandas jako operacja dzielenia ze względu na możliwość maskowania danych), podczas gdy rozumienie list jest nową definicją obiektu. Zobacz indeksowanie NumPy
Michael Delgado,
45
>>> x = [1,2,3,4,5]
>>> x<2
False
>>> x[False]
1
>>> x[True]
2

Wartość bool jest po prostu konwertowana na liczbę całkowitą. Indeks wynosi 0 lub 1.

Karoly Horvath
źródło
7
Możesz o tym wspomnieć xi 2być uporządkowane konsekwentnie, ale dowolnie oraz że kolejność może się zmienić w różnych implementacjach Pythona.
Robᵩ
2
Dodam też, że jest to sprytny sposób na robienie rzeczy i moim zdaniem należy go unikać. Zrób to wyraźnie - fakt, że OP musiał zadać to pytanie, potwierdza mój punkt widzenia.
kratenko
11
czy możesz dodać więcej szczegółów, dlaczego x<2 == false?
Iłya Bursov
15
boolnie jest konwertowane na liczbę całkowitą, a boolw Pythonie jest liczbą całkowitą
Antti Haapala
2
Właśnie w celu wyjaśnienia @ oświadczenie AnttiHaapala za kogoś innego, który przychodzi wraz, bool jest podklasą z int.
porglezomp,
14

Oryginalny kod w twoim pytaniu działa tylko w Pythonie 2. Jeśli xjest listw Pythonie 2, porównanie x < yjest Falsewtedy, gdy yjest integer. Dzieje się tak, ponieważ nie ma sensu porównywanie listy z liczbą całkowitą. Jednak w Pythonie 2, jeśli operandy nie są porównywalne, porównanie opiera się w języku CPython na kolejności alfabetycznej nazw typów ; dodatkowo wszystkie liczby zajmują pierwsze miejsce w porównaniach typu mieszanego . Nie jest to nawet opisane w dokumentacji CPythona 2, a różne implementacje Pythona 2 mogą dawać różne wyniki. Wartość ta jest [1, 2, 3, 4, 5] < 2obliczana na, Falseponieważ 2jest liczbą, a zatem „mniejszą” niż listw CPythonie. Ostatecznie doszło do tego mieszanego porównaniauznana za zbyt mało znaną funkcję i została usunięta w Pythonie 3.0.


Teraz wynik <jest bool; i booljest podklasą zint :

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

Więc w zasadzie bierzesz element 0 lub 1 w zależności od tego, czy porównanie jest prawdziwe, czy fałszywe.


Jeśli wypróbujesz powyższy kod w Pythonie 3, otrzymasz TypeError: unorderable types: list() < int()ze względu na zmianę w Pythonie 3.0 :

Porównania zamówień

Python 3.0 uprościł zasady porządkowania porównań:

Operatorzy porównania zamawiania ( <, <=, >=, >) podnieść TypeErrorwyjątek, gdy argumenty nie mają znaczącego naturalną kolejność. W ten sposób wyrażenia podoba 1 < '', 0 > Noneczy len <= lennie są już ważne, a np None < Nonepodbicia TypeErrorzamiast wrócić False. Konsekwencją jest to, że sortowanie niejednorodnej listy nie ma już sensu - wszystkie elementy muszą być ze sobą porównywalne. Zauważ, że nie dotyczy to operatorów ==i !=: obiekty różnych nieporównywalnych typów zawsze porównują ze sobą nierówne.


Istnieje wiele typów danych, które przeciążają operatory porównania, aby zrobić coś innego (ramki danych z pand, tablice numpy). Jeśli kod, którego używałeś, zrobił coś innego, to dlatego, że niex był alist , ale instancją innej klasy z <przesłoniętym operatorem w celu zwrócenia wartości, która nie jest bool; i ta wartość była następnie obsługiwana specjalnie przez x[](aka __getitem__/ __setitem__)

Antti Haapala
źródło
6
+FalseCześć Perl, hej JavaScript, jak się macie?
kot
@cat w Javascript, Perl, konwertuje wartość na liczbę. W Pythonie jest to UNARY_POSITIVEopcode, który wywołuje__pos__
Antti Haapala
Myślę, że miałeś na myśli __setitem__zamiast __getitem__w swojej ostatniej sekcji. Mam również nadzieję, że nie masz nic przeciwko temu, że moja odpowiedź została zainspirowana tą częścią Twojej odpowiedzi.
MSeifert,
Nie, miałem na myśli i myślałem, __getitem__choć równie dobrze mogło być __setitem__i__delitem__
Antti Haapala,
9

Ma to jeszcze jedno zastosowanie: golf kodowy. Code golf to sztuka pisania programów, które rozwiązują jakiś problem w jak najmniejszej liczbie bajtów kodu źródłowego.

return(a,b)[c<d]

jest w przybliżeniu odpowiednikiem

if c < d:
    return b
else:
    return a

z wyjątkiem tego, że zarówno a, jak i b są oceniane w pierwszej wersji, ale nie w drugiej.

c<docenia do Truelub False.
(a, b)jest krotką.
Indeksowanie krotki działa jak indeksowanie listy: (3,5)[1]== 5.
Truejest równa 1i Falserówna się 0.

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

lub dla False:

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

W sieci wymiany stosów znajduje się dobra lista wielu paskudnych rzeczy, które możesz zrobić w Pythonie, aby zaoszczędzić kilka bajtów. /codegolf/54/tips-for-golfing-in-python

Chociaż w normalnym kodzie nigdy nie powinno to być używane, aw twoim przypadku oznaczałoby to, że xdziała zarówno jako coś, co można porównać do liczby całkowitej, jak i jako kontener obsługujący cięcie na plasterki, co jest bardzo nietypową kombinacją. To prawdopodobnie kod Numpy, jak zauważyli inni.

Filip Haglund
źródło
6
Code Golf is the art of writing programs: ')
kot
1
Drobny czepiak: bool nie jest rzutowany na int, po prostu jest jeden (zobacz inne odpowiedzi)
kot
6

Ogólnie może to oznaczać wszystko . Było to już wyjaśniono, co to znaczy, jeśli xjest listalbo numpy.ndarrayale w ogóle to zależy tylko od tego, jak operatorzy porównania ( <, >...), a także w jaki sposób get / set-item ( [...]-syntax) są realizowane.

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!

Dlatego:

  • x < value jest równa x.__lt__(value)
  • x[value] jest (w przybliżeniu) odpowiednikiem x.__getitem__(value)
  • x[value] = othervaluejest (również w przybliżeniu) równoważne z x.__setitem__(value, othervalue).

Można to dostosować, aby robić wszystko, co chcesz. Na przykład (naśladuje nieco numpys-boolean indeksowanie):

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

Zobaczmy teraz, co się stanie, jeśli go użyjesz:

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

Zauważ, że to tylko jedna możliwość. Możesz wdrożyć prawie wszystko, co chcesz.

MSeifert
źródło
Powiedziałbym, że używanie czegokolwiek jest zbyt ogólne, aby zachować logicznie wytłumaczalne zachowanie, takie jak akceptowana odpowiedź.
PascalVKooten,
@PascalvKooten Czy nie zgadzasz się z „cokolwiek” lub z ogólną odpowiedzią? Myślę, że jest to ważna kwestia, ponieważ większość logicznych zachowań w Pythonie jest zgodna z konwencją.
MSeifert