Что означает x [x <2] = 0 в Python?

Я наткнулся на некоторый код с линией, похожей на

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]

Ответ 1

Это имеет смысл только при 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] изменяется.

Почему это происходит?

Правила сравнения:

  • При заказе двух строк или двух числовых типов упорядочение выполняется ожидаемым образом (лексикографическое упорядочение для строки, числовое упорядочение для целых чисел).

  • При заказе числового и нечислового типа сначала появляется числовой тип.

  • Когда вы заказываете два несовместимых типа, где ни один не является числовым, они упорядочены по алфавитному порядку их имен:

Итак, мы имеем следующий порядок

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

См. принятый ответ для Как Python сравнивает строку и int?.

Если x является массивом NumPy, то синтаксис имеет больше смысла из-за индексации булевых массивов. В этом случае x < 2 вообще не является булевым; это массив булевых элементов, представляющий, был ли каждый элемент x меньше 2. x[x < 2] = 0, затем выбирает элементы x, которые были меньше 2, и устанавливает эти ячейки в 0. См. Indexing.

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

Ответ 2

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

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

Ответ 3

Исходный код в вашем вопросе работает только в Python 2. Если x является list в Python 2, сравнение x < y равно False, если y является int eger. Это связано с тем, что нет смысла сравнивать список с целым числом. Однако в Python 2, если операнды не сопоставимы, сравнение основано на CPython на в алфавитном порядке имен названий типов; кроме того, все числа идут первыми в сравнении смешанного типа. Это даже не описано в документации CPython 2, и различные реализации Python 2 могут дать разные результаты. То есть [1, 2, 3, 4, 5] < 2 оценивается как False, потому что 2 является числом и, следовательно, "меньше", чем a list в CPython. Это смешанное сравнение в конечном итоге считалось слишком скрытым для функции и было удалено в Python 3.0.


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


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

Ответ 4

У этого есть еще одно использование: code golf. Code golf - это искусство написания программ, которые решают некоторые проблемы в максимально возможном количестве байтов исходного кода.

return(a,b)[c<d]

примерно эквивалентен

if c < d:
    return b
else:
    return a

за исключением того, что и а, и b оцениваются в первой версии, но не во второй версии.

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

  • (a,b)[c<d]
  • (a,b)[True]
  • (a,b)[1]
  • b

или для False:

  • (a,b)[c<d]
  • (a,b)[False]
  • (a,b)[0]
  • a

Там есть хороший список в сети обмена стеками из множества неприятных вещей, которые вы можете сделать для python, чтобы сохранить несколько байтов. https://codegolf.stackexchange.com/questions/54/tips-for-golfing-in-python

Хотя в нормальном коде это никогда не должно использоваться, и в вашем случае это означает, что x действует как то, что можно сравнить с целым числом, и как контейнер, который поддерживает нарезку, что является очень необычной комбинацией. Вероятно, это код Numpy, как указывали другие.

Ответ 5

В целом это может означать что-либо. Уже было объяснено, что это означает, если x является list или numpy.ndarray, но в целом это зависит только от того, как операторы сравнения (<, >,...), а также как get/set-item ([...] -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-boolean indexing):

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

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