Що означає x [x <2] = 0 у Python?


85

Я натрапив на якийсь код із рядком, подібним до

x[x<2]=0

Пограючись із варіаціями, я все ще застряг у тому, що робить цей синтаксис.

Приклади:

>>> 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
ніколи не має сенсу робити це зі списком.
dbliss

12
Це має сенс лише для масивів NumPy або подібних об’єктів, які поводяться абсолютно інакше, ніж поведінка у ваших експериментах, або поведінка на основі списку, пояснена в будь-якій відповіді.
user2357112 підтримує Моніку

11
Зауважте, це не працює в Python 3. Типи можна порівнювати лише тоді, коли порівняння має сенс. У Python 3 цей приклад кидає TypeError: unorderable types: list() < int().
Morgan Thrapp

2
Занадто мало інформації. Потрібно було згадувати, що масив - це масив numpy.
lmaooooo

3
Я вражений тим, що отримав так багато голосів (хоча це справді гарне питання для формату SO).
PascalVKooten

Відповіді:


120

Це має сенс лише для масивів NumPy . Поведінка зі списками є марною і специфічною для Python 2 (не Python 3). Можливо, вам доведеться ще раз перевірити, чи справді вихідний об'єкт був масивом NumPy (див. Далі), а не списком.

Але у вашому коді тут х - простий список.

Оскільки

x < 2

є False, тобто 0, отже

x[x<2] є x[0]

x[0] змінюється.

І навпаки, x[x>2]є x[True]абоx[1]

Отже, x[1]змінюється.

Чому так трапляється?

Правила порівняння:

  1. Коли ви замовляєте два рядки або два числові типи, упорядкування виконується очікуваним чином (лексикографічне впорядкування для рядка, числове впорядкування для цілих чисел).

  2. Коли ви замовляєте числовий і нечисловий тип, числовий тип стає на перше місце.

  3. Коли ви замовляєте два несумісні типи, жоден з яких не є числовим, вони впорядковуються за алфавітом їх назв типів:

Отже, маємо такий порядок

числовий <список <рядок <кортеж

Дивіться прийняту відповідь на тему Як Python порівнює рядок та int? .

Якщо x - масив NumPy , то синтаксис має більший сенс через індексацію булевого масиву . У такому випадку x < 2це зовсім не логічне значення; це масив булевих символів, що вказує, чи xне менше кожного елемента 2, x[x < 2] = 0потім виділяє елементи, xякі були менше 2, і встановлює для цих комірок значення 0. Див. Індексація .

>>> 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
З огляду на те, що в ОП конкретно сказано: "Я натрапив на такий код ...", я думаю, що ваша відповідь, що описує булеве індексування numpy, є дуже корисною - можливо, варто зазначити, що якщо ОП прокручує код, який вони переглядали, Майже напевно побачу для importfor numpy.
Річард Снейп,

2
Все-таки надто розумний спосіб це зробити, звичайно? (У порівнянні з, скажімо [0 if i < 2 else i for i in x],.) Або це заохочуваний стиль у Numpy?
Тім Педерік

6
@TimPederick: Використання розуміння списків з NumPy - досить погана ідея. Це в десятки-сотні разів повільніше, він не працює з довільно-розмірними масивами, простіше викрутити типи елементів і створює список замість масиву. Булеве індексування масиву цілком нормальне і очікуване в NumPy.
user2357112 підтримує Моніку

@TimPederick На додаток до хіту продуктивності, ймовірно, що той, хто написав код, мав намір продовжувати використовувати масив numpy. x[x<2]поверне масив numpy, тоді як [0 if i<2 else i for i in x]повертає список. Це пов’язано з тим, що x[x<2]є операцією індексування (згадана в numpy / scipy / pandas як операція нарізування завдяки можливості маскувати дані), тоді як розуміння списку - це нове визначення об’єкта. Див. Індексування NumPy
Майкл Дельгадо

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

Буль просто перетворюється на ціле число. Індекс дорівнює 0 або 1.


7
Можна сказати , що xі 2є « упорядкований послідовно , але довільно » і що впорядкування може змінитися в різних реалізаціях Python.
Робᵩ

2
Я також додав би, що це розумний спосіб робити речі, і, на мою думку, слід уникати. Робіть це явно - той факт, що ОП повинен був задати це питання, підтверджує мою думку.
кратенко

11
Ви можете додати більше деталей, чому x<2 == false?
Ілля Бурсов,

15
boolне перетворюється на ціле число, a boolв Python є цілим числом
Antti Haapala

2
Просто щоб прояснити @ заяву AnttiHaapala для кого -то ще , що приходить разом, bool є підкласом з int.
porglezomp

14

Оригінальний код у вашому запитанні працює лише на Python 2. Якщо xє listPython 2, порівняння x < y- Falseякщо yце intегер. Це тому, що немає сенсу порівнювати список із цілим числом. Однак у Python 2, якщо операнди не порівнянні, порівняння базується в CPython на алфавітному впорядкуванні імен типів ; додатково всі цифри стоять на першому місці при порівнянні змішаного типу . Це навіть не прописано в документації CPython 2, і різні реалізації Python 2 можуть дати різні результати. Це [1, 2, 3, 4, 5] < 2обчислюється, Falseтому що 2є числом і, отже, "меншим", ніж listу CPython. Врешті-решт це змішане порівняння булобуло визнано надто неясною функцією і було видалено в Python 3.0.


Тепер результат <є a bool; і boolце підклас зint :

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

Отже, в основному ви берете елемент 0 або 1 залежно від того, чи є порівняння істинним чи хибним.


Якщо ви спробуєте наведений вище код у Python 3, ви отримаєте TypeError: unorderable types: list() < int()завдяки зміні в Python 3.0 :

Порівняння замовлень

Python 3.0 спростив правила упорядкування порівнянь:

Оператори порівняння впорядкованості ( <, <=, >=, >) підняти TypeErrorвиняток , якщо операнди не мають змістовний природний порядок. Таким чином, вирази не подобається 1 < '', 0 > Noneабо len <= lenбільше не дійсні, і наприклад , None < Noneпідвищень TypeErrorзамість повернення False. Висновок полягає в тому, що сортування різнорідного списку більше не має сенсу - всі елементи повинні бути порівнянними між собою. Зауважте, що це не стосується операторів ==and !=: об’єкти різних незрівнянних типів завжди порівнюють нерівні між собою.


Існує багато типів даних, які перевантажують оператори порівняння, щоб зробити щось інше (фрейми даних з панд, масиви numpy). Якщо код , який ви використовували зробив що - то ще, це було тому , що xбув НЕlist , але з екземпляром деякого іншого класу з оператором <перевизначені повернути значення, яке не є bool; і це значення потім оброблялося спеціально x[](aka __getitem__/ __setitem__)


6
+FalseПривіт, Perl, привіт, JavaScript, як справи?
кішка

@cat у Javascript, Perl, він перетворює значення як число. У Python саме для UNARY_POSITIVEopcode називається__pos__
Antti Haapala

Я думаю, ви мали на увазі __setitem__замість того, щоб __getitem__у вашому останньому розділі. Також я сподіваюся, ви не проти, що моя відповідь була натхненна саме тією частиною вашої відповіді.
MSeifert

Ні, я мав на увазі і думав, __getitem__хоча однаково могло бути __setitem__і__delitem__
Антті Хаапала

9

Це має ще одне використання: код гольфу. Код-гольф - це мистецтво писати програми, які вирішують певні проблеми за якомога менше байт вихідного коду.

return(a,b)[c<d]

приблизно еквівалентно

if c < d:
    return b
else:
    return a

за винятком того, що і a, і b оцінюються в першій версії, але не в другій версії.

c<dоцінює до Trueабо False.
(a, b)є кортежем.
Індексація на кортежі працює як індексація у списку: (3,5)[1]== 5.
Trueдорівнює 1і Falseдорівнює 0.

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

або для False:

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

У мережі обміну стеками є хороший список багатьох неприємних речей, які ви можете зробити з python, щоб заощадити кілька байтів. /codegolf/54/tips-for-golfing-in-python

Хоча в звичайному коді цього ніколи не слід використовувати, і у вашому випадку це означатиме, що він xвиступає і як щось, що можна порівняти з цілим числом, і як контейнер, що підтримує нарізання, що є дуже незвичною комбінацією. Це, мабуть, код Numpy, як вказували інші.


6
Code Golf is the art of writing programs: ')
кішка

1
Незначні чіплятися: BOOL НЕ відкидати до міжнар, він просто є один (див інших відповідей)
кіт

6

Загалом це може означати що завгодно . Це вже було пояснено , що це означає , якщо xце listабо , numpy.ndarrayале в цілому це залежить тільки від того, як оператори порівняння ( <, >, ...) , а також як отримати / встановити-елемент ( [...]реалізуються -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!

Оскільки:

  • x < value еквівалентно x.__lt__(value)
  • x[value] (приблизно) еквівалентно x.__getitem__(value)
  • x[value] = othervalueє (також приблизно) еквівалентно x.__setitem__(value, othervalue).

Це можна налаштувати, щоб робити все, що завгодно. Просто як приклад (імітує трохи numpys-булеве індексування):

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

Тож давайте подивимося, що станеться, якщо ви ним скористаєтесь:

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

Зверніть увагу, що це лише одна можливість. Ви можете реалізувати майже все, що забажаєте.


Я б сказав, що використання чого-небудь насправді є занадто загальним для логічно пояснюваної поведінки, як прийнята відповідь.
PascalVKooten

@PascalvKooten Ви не погоджуєтеся з "чимось" чи з узагальненою відповіддю? Я думаю, що це важливий момент, оскільки більшість логічних поведінок у python - це лише за домовленістю.
MSeifert
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.