Что означает 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().
Морган Трапп,
2
Слишком мало информации. Надо было упомянуть, что массив - это массив numpy.
lmaooooo
3
Я шокирован, что за него так много голосов (хотя это действительно хороший вопрос для формата SO).
PascalVKooten

Ответы:

120

Это имеет смысл только с массивами NumPy . Поведение со списками бесполезно и специфично для Python 2 (не Python 3). Вы можете дважды проверить, действительно ли исходный объект был массивом NumPy (см. Ниже), а не списком.

Но в вашем коде x - это простой список.

поскольку

x < 2

ложно, т.е. 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
trans1st0r
источник
11
Учитывая, что OP конкретно говорит: «Я наткнулся на такой код ...», я думаю, что ваш ответ, описывающий numpy boolean indexing, очень полезен - возможно, стоит указать, что если OP прокручивает код, на который они смотрели, они ' я почти наверняка увидим importnumpy.
J Ричард Снейп,
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

Bool просто преобразуется в целое число. Индекс равен 0 или 1.

Кароли Хорват
источник
7
Вы можете упомянуть, что xи 2они « упорядочены последовательно, но произвольно » и что порядок может меняться в разных реализациях Python.
Robᵩ
2
Я бы также добавил, что это умный способ, и, на мой взгляд, его следует избегать. Сделайте это явно - тот факт, что OP должен был задать этот вопрос, подтверждает мою точку зрения.
Кратенко
11
можешь добавить больше деталей, почему x<2 == false?
Илья Бурсов
15
boolне преобразуется в целое число, a boolв Python - это целое число
Антти Хаапала
2
Просто чтобы прояснить @ заявление AnttiHaapala для кого -то еще , что приходит вместе, bool является подклассом из int.
porglezomp
14

Оригинальный код в ваш вопрос работает только в Python 2. Если xэто listв Python 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поднимаются . Как следствие, сортировка разнородного списка больше не имеет смысла - все элементы должны быть сопоставимы друг с другом. Обратите внимание , что это не относится к и операторам: объекты различных несравнимых типов всегда сравнивать неравны друг с другом.TypeErrorFalse==!=


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

Антти Хаапала
источник
6
+FalseПривет, Perl, привет, JavaScript, как дела?
cat
@cat в Javascript, Perl преобразует значение в число. В Python это UNARY_POSITIVEкод операции, который вызывает__pos__
Антти Хаапала,
Я думаю, вы имели в виду, __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: ')
cat
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])

Обратите внимание, это всего лишь одна возможность. Вы можете реализовать практически все, что захотите.

MSeifert
источник
Я бы сказал, что использование чего-либо на самом деле слишком универсально для логически объяснимого поведения, такого как принятый ответ.
PascalVKooten
@PascalvKooten Вы не согласны с «чем-нибудь» или с обобщенным ответом? Я считаю, что это важный момент, потому что наиболее логичное поведение в python является условным.
MSeifert