Каков приоритет оператора при написании двойного неравенства в Python (явно в коде, и как это можно переопределить для массивов?)

Какой конкретный код, по порядку, выполняется, когда я прошу что-то вроде

>>> 1 <= 3 >= 2
True

Если оба имеют одинаковый приоритет, и это просто порядок их оценки, почему второе неравенство функционирует как (3 >= 2) вместо (True >= 2)

Рассмотрим, например, разницу между этими

>>> (1 < 3) < 2
True

>>> 1 < 3 < 2
False

Является ли это просто чисто синтаксическим сокращением, жестко запрограммированным в Python, чтобы расширить второй как and из двух утверждений?

Могу ли я изменить это поведение для класса, так что a <= b <= c расширяется к чему-то другому? Похоже, что следующий случай

a (logical operator) b (logical operator) c 
    --> (a logical operator b) and (b logical operator c)

но реальный вопрос в том, как это реализуется в коде.

Мне любопытно, чтобы я мог повторить этот вид __lt__ а также __gt__ поведение в некоторых из моих собственных классов, но я не понимаю, как это сделать, поддерживая средний аргумент постоянным.

Вот конкретный пример:

>>> import numpy as np

>>> tst = np.asarray([1,2,3,4,5,6])

>>> 3 <= tst
array([False, False,  True,  True,  True,  True], dtype=bool)

>>> 3 <= tst <= 5
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
/home/ely/<ipython-input-135-ac909818f2b1> in <module>()
----> 1 3 <= tst <= 5

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

Было бы неплохо переопределить это так, чтобы оно "просто работало" и с массивами, например так:

>>> np.logical_and(3 <= tst, tst <= 5)
array([False, False,  True,  True,  True,  False], dtype=bool)

Добавлено для уточнения

В комментариях указано, что я плохо объяснил вопрос. Вот некоторые уточняющие замечания:

1) Я не ищу простого объяснения того факта, что переводчик and между двумя связанными неравенствами. Я уже знал это и сказал выше.

2) Для аналогии с тем, что я хочу сделать, рассмотрим with заявление ( ссылка). Следующие:

with MyClass(some_obj) as foo:
    do_stuff()

распаковывает в

foo = MyClass(some_obj)
foo.__enter__()
try:
    do_stuff()
finally:
    foo.__exit__()

Итак, написав MyClass соответственно, я могу сделать много особых вещей внутри with заявление.

Я спрашиваю, существует ли аналогичная распаковка кода из связанного неравенства, с помощью которого я могу перехватить то, что он делает, и перенаправить его на использование логических операторов в стиле массива вместо тех классов, которые мне нужны.

Я чувствую, что это очень ясно из моего вопроса, особенно из примера, но, надеюсь, это проясняет ситуацию.

4 ответа

Решение

Я не совсем уверен, что вы ищете, но быстрая разборка показывает, что a < b < c не компилируется в тот же байт-код, что и a < b and b < c

>>> import dis
>>>
>>> def f(a, b, c):
...     return a < b < c
...
>>> dis.dis(f)
  2           0 LOAD_FAST                0 (a)
              3 LOAD_FAST                1 (b)
              6 DUP_TOP
              7 ROT_THREE
              8 COMPARE_OP               0 (<)
             11 JUMP_IF_FALSE_OR_POP    21
             14 LOAD_FAST                2 (c)
             17 COMPARE_OP               0 (<)
             20 RETURN_VALUE
        >>   21 ROT_TWO
             22 POP_TOP
             23 RETURN_VALUE
>>>
>>> def f(a, b, c):
...     return a < b and b < c
...
>>> dis.dis(f)
  2           0 LOAD_FAST                0 (a)
              3 LOAD_FAST                1 (b)
              6 COMPARE_OP               0 (<)
              9 JUMP_IF_FALSE_OR_POP    21
             12 LOAD_FAST                1 (b)
             15 LOAD_FAST                2 (c)
             18 COMPARE_OP               0 (<)
        >>   21 RETURN_VALUE

Редактировать 1: Копать дальше, я думаю, что это странно или неправильно с NumPy. Рассмотрим этот пример кода, я думаю, что он работает так, как вы ожидаете.

class Object(object):
    def __init__(self, values):
        self.values = values
    def __lt__(self, other):
        return [x < other for x in self.values]
    def __gt__(self, other):
        return [x > other for x in self.values]

x = Object([1, 2, 3])
print x < 5 # [True, True, True]
print x > 5 # [False, False, False]
print 0 < x < 5 # [True, True, True]

Редактировать 2: На самом деле это не работает "должным образом"...

print 1 < x # [False, True, True]
print x < 3 # [True, True, False]
print 1 < x < 3 # [True, True, False]

Я думаю, что это сравнивает логические значения с числами во втором сравнении 1 < x < 3,

Редактировать 3: Мне не нравится идея возврата не-булевых значений из специальных методов gt, lt, gte, lte, но на самом деле это не ограничено согласно документации Python.

http://docs.python.org/reference/datamodel.html л

По соглашению False и True возвращаются для успешного сравнения. Однако эти методы могут возвращать любое значение...

Оба имеют одинаковый приоритет, но оцениваются слева направо согласно документации. Выражение формы a <= b <= c расширяется до a <= b and b <= c,

но реальный вопрос в том, как это реализуется в коде.

Вы имеете в виду, как переводчик это преобразовывает, или как? Ты уже сказал

a (logical operator) b (logical operator) c 
    --> (a logical operator b) and (b logical operator c)

поэтому я не уверен, что вы спрашиваете здесь хорошо, я понял: нет, вы не можете переопределить расширение из a < b < c в (a < b) and (b < c) IIUC.


Мне любопытно, чтобы я мог повторить этот вид __lt__ а также __gt__ поведение в некоторых из моих собственных классов, но я не понимаю, как это сделать, поддерживая средний аргумент постоянным.

Это зависит от того, какой из a, b а также c в выражении a < b < c являются экземплярами вашего собственного класса. Реализация вашего __lt__ а также __gt__ и методы получают некоторые пути, но документация указывает, что:

Не существует версий этих методов со свопированными аргументами (которые будут использоваться, когда левый аргумент не поддерживает операцию, но правый аргумент поддерживает)

Итак, если вы хотите Int < MyClass < Int, не повезло тебе. Вам нужно, как минимум, MyClass < MyClass < Something (таким образом, экземпляр вашего класса находится на LHS каждого сравнения в расширенном выражении).

Я хотел ответить по поводу исходного примера:

Почему:(1 < 3) < 2 == True

Пока:1 < 3 < 2 == False

Итак, давайте разберемся, второе (очевидное) первое:

(1 < 3) and (3 < 2)упрощается до(True) and (False)которыйFalse

Далее, менее очевидный:

(1 < 3) < 2что упрощается до(True) < 2что упрощается до1 < 2, которыйTrue.

Вот еще один ответ, который объясняет, что это связано с тем, что логические значения являются подтипом целых чисел: /questions/20083183/pochemu-python-schitaet-chto-logicheskoe-znachenie-yavlyaetsya-tselyim-chislom/20083184#20083184

Вот также официальная документация по логическому целочисленному типу: https://docs.python.org/3/c-api/bool.html?highlight=boolean%20int

Другие вопросы по тегам