Отслеживание Python выражение выражения шаг за шагом

Я пытаюсь написать визуализатор оценки выражений Python, который покажет, как выражения Python оцениваются шаг за шагом (в образовательных целях). Python Tutor от Philip Guo великолепен, но он оценивает программу Python построчно, и я обнаружил, что студенты иногда не понимают, как выражения с одной строкой похожи sorted([4, 2, 3, 1] + [5, 6])[1] == 2 оцениваются, и я хотел бы визуализировать этот процесс. (Кажется, никто еще этого не делал - по крайней мере, я ничего не нашел.) Идеальное решение создаст последовательность строк, подобную этой:

sorted([4, 2, 3, 1] + [5, 6])[1] == 2
sorted( >> [4, 2, 3, 1] + [5, 6] << )[1] == 2
>> sorted([4, 2, 3, 1, 5, 6]) << [1] == 2
>> [1 2 3 4 5 6][1] << == 2
>> 2 == 2 <<
True

Вот >> а также << используются для выделения части выражения, которая оценивается на текущем шаге и затем заменяется ее значением. (Возможно, позже я попытаюсь преобразовать эту последовательность в какую-то анимацию.)

Моя текущая стратегия заключается в использовании ast.parse() проанализировать строку в AST, затем найти узел, который будет оцениваться первым, оценить его с помощью eval(compile(node, '', 'eval')) (Я определенно не хочу переопределять весь Python:)), преобразовать результат оценки в AST Node (с помощью repr а потом ast.parse()?) и замените текущий узел на узел результата, затем используйте codegen.to_source создать измененную строку кода из (модифицированного) AST и продолжать тот же процесс, пока у меня не будет только одного литерала в дереве.

Мой вопрос: как мне найти узел, который будет оценен первым? Кажется, что я могу пройти дерево в глубину с помощью подклассов ast.NodeVisitor, но я не уверен, как я могу определить, что достиг нужного узла, и как мне прекратить обход после него?


РЕДАКТИРОВАТЬ.

Возможно, что мой первоначальный подход с преобразованием дерева неосуществим. На самом деле, элементарный шаг оценки выражения Python не обязательно должен быть заменой некоторого подвыражения на более простое (как в арифметике). Например, списочное понимание обеспечивает гораздо более сложное поведение, которое не может быть выражено в терминах, замените эту вещь этой вещью, а затем повторите рекурсивно. Поэтому я переформулирую вопрос немного. Мне нужен какой-то способ программно показать, как выражения Python оцениваются шаг за шагом. Например, функция трассировки MacroPy, упомянутая @jasonharper, является приемлемым решением на данном этапе. К сожалению, MacroPy, похоже, заброшен и не работает с Python 3. Есть ли идеи, как походить на это поведение трассировки в Python 3 без портирования полного MacroPy?


EDIT2.

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

3 ответа

Решение

Степпинг выражений реализован в Thonny IDE.

Он использует инструментарий AST, где каждое (под) выражение e превращается в after(before(<location info>), e), функции before а также after являются фиктивными функциями для вызова дополнительных call- событий в системе трассировки Python. Эти дополнительные вызовы уведомляют, когда (под) оценка выражения собирается начать или только что закончилась. (Подобные фиктивные функции добавляются для определения начала и конца каждого оператора.)

Инструментарий AST и интерпретация этих новых событий выполняются в thonny.backend.FancyTracer.

Узлы Python AST содержат начальную позицию соответствующих текстовых диапазонов, но иногда они неверны. Конечные позиции полностью отсутствуют. thonny.ast_utils.mark_text_ranges пытается позаботиться об этом (но решение на данный момент не завершено).

Было бы неплохо, если бы кто-то извлек соответствующую функциональность из Thonny в более общий пакет. Может быть, даже два пакета - один для вычисления информации о местоположении для Python AST и другой для детальной трассировки кода Python. Я был бы готов помочь с этим, если бы кто-то взял на себя инициативу.

Почему бы не использовать dis модуль?

Поскольку CPython компилирует Python в байт-код и запускает его, просмотр байт-кода дает вам лучшее представление о том, что на самом деле происходит.

In [1]: import dis

In [2]: dis.dis('sorted([4, 2, 3, 1] + [5, 6])[1] == 2')
  1           0 LOAD_NAME                0 (sorted)
              3 LOAD_CONST               0 (4)
              6 LOAD_CONST               1 (2)
              9 LOAD_CONST               2 (3)
             12 LOAD_CONST               3 (1)
             15 BUILD_LIST               4
             18 LOAD_CONST               4 (5)
             21 LOAD_CONST               5 (6)
             24 BUILD_LIST               2
             27 BINARY_ADD
             28 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             31 LOAD_CONST               3 (1)
             34 BINARY_SUBSCR
             35 LOAD_CONST               1 (2)
             38 COMPARE_OP               2 (==)
             41 RETURN_VALUE

Изменить: Альтернативный метод может быть показать шаги один за другим в IPython:

In [1]: [4, 2, 3, 1]
Out[1]: [4, 2, 3, 1]

In [2]: [4, 2, 3, 1] + [5, 6]
Out[2]: [4, 2, 3, 1, 5, 6]

In [3]: sorted([4, 2, 3, 1, 5, 6])
Out[3]: [1, 2, 3, 4, 5, 6]

In [4]: [1, 2, 3, 4, 5, 6][1]
Out[4]: 2

In [5]: 2 == 2
Out[5]: True

Добавление двух списков, конечно, не первый узел, который будет оцениваться в этом коде; Я считаю, что на самом деле существует девять ранних оценок узлов - sorted, 4, 2, 3, 1, [4,2,3,1], 5, 6, [5,6], Вам нужно будет не только определить, в каких оценках выполняются заказы, но и решить, какие из этих оценок стоит показать.

Я думаю, что лучшим подходом к вашей проблеме было бы изменение узлов AST, чтобы они испускали свое состояние до / после как побочный эффект от выполнения. Вас не заботит их порядок, вы просто выполните все выражение один раз. И уже есть пакет под названием macropy, у которого есть функция трассировки, которая делает именно это. Его вывод не совсем то, что вы просите, но, вероятно, его можно изменить, чтобы он стал ближе.

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