Как на самом деле работают цепные сравнения в Python?
Python Doc для сравнения говорит:
Сравнения могут быть связаны произвольно, например,
x < y <= z
эквивалентноx < y and y <= z
, за исключением того, чтоy
оценивается только один раз (но в обоих случаяхz
не оценивается вообще, когдаx < y
оказывается ложным).
И эти так вопросы / ответы проливают еще немного света на такое использование:
- операторы сравнения Python цепочка / группировка слева направо?
- что делает "оценено только один раз" означает для цепных сравнений в Python?, в частности принятый в настоящее время ответ
Итак, что-то вроде (надуманный пример):
if 1 < input("Value:") < 10: print "Is greater than 1 and less than 10"
Запрашивает ввод только один раз. В этом есть смысл. И вот что:
if 1 < input("Val1:") < 10 < input("Val2:") < 20: print "woo!"
Только просит Val2
Если Val1
находится между 1 и 10 и только печатает "ууу!"Если Val2
также между 10 и 20 (доказывая, что они могут быть "скованы произвольно"). Это тоже имеет смысл.
Но я ... все еще любопытно, как это на самом деле реализуется/интерпретируется на уровне лексера/парсера/компилятора (или чего-то еще).
Первый пример выше в основном реализован следующим образом:
x = input("Value:")
1 < x and x < 10: print "Is between 1 and 10"
Где x
реально существует (и фактически по существу не имеет названия) только для этих сравнений? Или это каким-то образом заставляет оператор сравнения возвращать и булев результат, и оценку правильного операнда (который будет использоваться для дальнейшего сравнения) или что-то в этом роде?
Расширение анализ второго примера приводит меня к мысли, что он использует что-то вроде безымянного промежуточного результата (кто-то просветит меня, если для этого есть термин), поскольку он не оценивает все операнды перед сравнением.
1 ответ:
Вы можете просто позволить Python сказать вам, какой байт-код создается с помощью
dis
модуль :>>> import dis >>> def f(): return 1 < input("Value:") < 10 ... >>> dis.dis(f) 1 0 LOAD_CONST 1 (1) 3 LOAD_GLOBAL 0 (input) 6 LOAD_CONST 2 ('Value:') 9 CALL_FUNCTION 1 12 DUP_TOP 13 ROT_THREE 14 COMPARE_OP 0 (<) 17 JUMP_IF_FALSE_OR_POP 27 20 LOAD_CONST 3 (10) 23 COMPARE_OP 0 (<) 26 RETURN_VALUE >> 27 ROT_TWO 28 POP_TOP 29 RETURN_VALUE
Python использует стек;
CALL_FUNCTION
байт-код использует элементы в стеке (input
global и'Value:'
string) для вызова функции с одним аргументом, чтобы заменить эти два элемента в стеке результатом вызова функции. Перед вызовом функции константа1
загружалась в стек.Так что к тому времени
input
был назван стек выглядит например:input_result 1
И
DUP_TOP
дублирует верхнее значение, прежде чем вращает три верхних стека значений, чтобы получить:1 input_result input_result
И a
COMPARE_OP
, чтобы проверить два верхних элемента с помощью<
, заменив два верхних элемента результатом.Если этот результат был
False
JUMP_IF_FALSE_OR_POP
байт-код переходит к 27, который поворачиваетFalse
сверху с оставшимсяinput_result
, чтобы очистить его с помощьюPOP_TOP
, чтобы затем вернуть оставшеесяFalse
верхнее значение в качестве результат.Если результат
True
однако, это значение выскакивает из стека байт-кодомJUMP_IF_FALSE_OR_POP
и на его место загружается значение10
сверху, и мы получаем:10 input_result
И вместо этого делается и возвращается другое сравнение.
Таким образом, по существу Python делает следующее:
stack_1 = stack_2 = input('Value:') if 1 < stack_1: result = False else: result = stack_2 < 10
Со значениями
stack_*
, очищенными снова.Стек, таким образом, содержит неназванный промежуточный результат для сравнения