Как троичный оператор реализован в Python - PullRequest
0 голосов
/ 06 сентября 2018

Я понимаю, что условные выражения (или троичные операторы) ленивы в Python. Они представляют собой условное выполнение , а не условное выделение . Другими словами, только один из a или b оценивается следующим образом:

c = a if condition else b

Мне интересно знать, как это реализовано внутри. Преобразует ли Python оператор if, как показано ниже, и, если да, на каком этапе происходит это преобразование?

if condition:
    c = a
else:
    c = b

Или троичный оператор на самом деле является отдельным и отдельным выражением, определенным совершенно отдельно? Если да, могу ли я получить доступ к коду CPython для условных выражений?

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


Редактировать: Вы можете принять эталонную реализацию CPython.

Ответы [ 3 ]

0 голосов
/ 06 сентября 2018

Преобразует ли Python в оператор if, как показано ниже

Почти.

import dis

def trenary():
    x = 'a' if 1 == 1 else 'b'

def normal_if():
    if 1 == 1:
        c = 'a'
    else:
        c = 'b'

print('trenary')
dis.dis(trenary)
print()
print('normal if')
dis.dis(normal_if)

Это выводит:

trenary
 68           0 LOAD_CONST               1 (1)
              2 LOAD_CONST               1 (1)
              4 COMPARE_OP               2 (==)
              6 POP_JUMP_IF_FALSE       12
              8 LOAD_CONST               2 ('a')
             10 JUMP_FORWARD             2 (to 14)
        >>   12 LOAD_CONST               3 ('b')
        >>   14 STORE_FAST               0 (x)
             16 LOAD_CONST               0 (None)
             18 RETURN_VALUE

normal if
 71           0 LOAD_CONST               1 (1)
              2 LOAD_CONST               1 (1)
              4 COMPARE_OP               2 (==)
              6 POP_JUMP_IF_FALSE       14

 72           8 LOAD_CONST               2 ('a')
             10 STORE_FAST               0 (c)
             12 JUMP_FORWARD             4 (to 18)

 74     >>   14 LOAD_CONST               3 ('b')
             16 STORE_FAST               0 (c)
        >>   18 LOAD_CONST               0 (None)
             20 RETURN_VALUE

Они выглядят почти одинаково, за исключением местоположения JUMP_FORWARD и дополнительного STORE_FAST, на что указывает @ L3viathan.

Мы также получаем почти одинаковое время выполнения (с незначительной разницей):

from timeit import Timer

print(min(Timer(trenary).repeat(5000, 5000)))
print(min(Timer(normal_if).repeat(5000, 5000)))
# 0.0006442809999998023
# 0.0006442799999994975

Что касается , когда происходит это преобразование, я бы предположил когда-нибудь во время "компиляции" в байт-код.

0 голосов
/ 06 сентября 2018

Что

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

value_a if condition else value_b является функциональным и возвращает значение value_a или value_b.

if condition then:
   do_a
else:
   do_b

является процедурным, это do_a или do_b.

Примечание: Процедурный - это делать, делать то, то или то. Функциональность - это ценность, это или это так.

Как

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

0 голосов
/ 06 сентября 2018

Python не должен ничего преобразовывать и не может, если захочет.

Условное выражение анализируется с помощью грамматики языка в абстрактном синтаксическом дереве , которое, в свою очередь, затем компилируется в байт-код. Вы можете произвести AST с помощью функции ast.parse() :

>>> import ast
>>> ast.parse('c = a if condition else b').body[0]  # first statement in the tree
<_ast.Assign object at 0x10f05c550>
>>> ast.dump(ast.parse('c = a if condition else b').body[0])
"Assign(targets=[Name(id='c', ctx=Store())], value=IfExp(test=Name(id='condition', ctx=Load()), body=Name(id='a', ctx=Load()), orelse=Name(id='b', ctx=Load())))"

Обратите внимание на узел ast.IfExp() в AST, созданный для назначения; это выделенный узел для условных выражений. Он состоит из частей test, body и orelse, представляющих 3 выражения, составляющие части условия, истину и ложь. Это задокументировано в модуле ast Абстрактная грамматика раздел :

expr = [...]
     | [...]
     | IfExp(expr test, expr body, expr orelse)

Это показывает, что типом каждого элемента является другой expr узел выражения.

Затем дерево разбора компилируется в байт-код, который использует стек для условного перехода к правой части на основе теста; мы можем передать AST, создаваемый ast.parse(), напрямую в функцию compile() , после чего модуль dis позволяет нам взглянуть на удобную для человека форму производимого байт-кода по компиляции:

>>> import dis
>>> dis.dis(compile(ast.parse('c = a if condition else b'), '', 'exec'))
  1           0 LOAD_NAME                0 (condition)
              2 POP_JUMP_IF_FALSE        8
              4 LOAD_NAME                1 (a)
              6 JUMP_FORWARD             2 (to 10)
        >>    8 LOAD_NAME                2 (b)
        >>   10 STORE_NAME               3 (c)
             12 LOAD_CONST               0 (None)
             14 RETURN_VALUE

Таким образом, если условие ложно, цикл интерпретатора переходит к инструкции 8, в противном случае инструкции 4 и 6 выполняются, а инструкция 6 переходит к инструкции 10 (то есть после выражения else). Конечным результатом является то, что либо инструкция 4, либо инструкция 8 помещают новый результат в начало стека, чтобы STORE_NAME мог перейти к переменной.

Оператор if приводит к другому узлу AST, и результирующий байт-код оказывается очень похожим в том смысле, что он тоже будет использовать переходы. Но компилятор обрабатывает их как отдельные части синтаксиса, и он имеет до.

Выражения и операторы являются двумя очень разными фундаментальными строительными блоками языка программирования. Выражения могут содержать выражения, но выражения не могут содержать выражения, только другие выражения. И выражения могут создавать значение (для использования окружающего синтаксиса), но операторы не могут . Таким образом, Python должен относиться к условным выражениям очень иначе, чем к операторам, в том смысле, что синтаксический анализатор грамматики знает, когда ожидать оператор и когда выражение разрешено. Если вы преобразуете условное выражение в оператор, вы никогда не сможете использовать такое выражение как часть большего выражения!

Поскольку оператор if является , а не выражением , он не возвращает значение (так как только выражения могут создавать значение), и поэтому результирующий байт-код не будет создайте значение в верхней части стека, которое будет использоваться окружающим кодом Python (нет c = if condition : ...). if операторы содержат условное выражение и suite , которые всегда должны состоять из большего количества операторов (существует такая вещь, как «выражение-выражение», которое позволяет вам просто поставить выражения в операторе (например, 1 + 1 в одной строке), и эти операторы могут «делать что-то», например, присваивать или возвращать из функции, но ничто из того, что они делают, не заставит if вернуть что-либо.

Это отражено в определении узла AST для операторов if:

stmt =  [...]
      | [...]
      | If(expr test, stmt* body, stmt* orelse)

Таким образом, для узла If, test является единственным узлом выражения, а body и orelse оба состоят из ноль или более операторов. Часть orelse будет содержать любые elif ...: тесты как дополнительные If() узлы или любой другой тип оператора для формирования безусловного else:. С нулевым или большим количеством элементов вы не можете ожидать одного результата.

Так что это не уникально для CPython, это относится ко всем реализациям Python. Python грамматика не является деталью реализации.

...