Я наткнулся на некоторый код с линией, похожей на
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]
Я наткнулся на некоторый код с линией, похожей на
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]
Это имеет смысл только при 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
>>> x = [1,2,3,4,5]
>>> x<2
False
>>> x[False]
1
>>> x[True]
2
bool просто преобразуется в целое число. Индекс равен 0 или 1.
Исходный код в вашем вопросе работает только в 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__
)
У этого есть еще одно использование: 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, как указывали другие.
В целом это может означать что-либо. Уже было объяснено, что это означает, если 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])
Обратите внимание, что это всего лишь одна возможность. Вы можете реализовать практически все, что хотите.