Как отформатировать синтаксический анализ AST

#python #python-3.x #abstract-syntax-tree

#python #python-3.x #абстрактное синтаксическое дерево

Вопрос:

Я хотел бы отформатировать следующий синтаксический анализ ast:

 >>> import ast
>>> print(ast.dump(ast.parse('-a b')))
Module(body=[Expr(value=BinOp(left=UnaryOp(op=USub(), operand=Name(id='a', ctx=Load())), op=Add(), right=Name(id='b', ctx=Load())))])
 

Похоже, что эта indent опция была введена в python3.9, но я не вижу возможности «pretty-print» до этого. Какие существуют варианты для печати вывода с хорошим форматированием для синтаксического дерева?

Ответ №1:

Если вам нужно распечатать AST в более ранней версии python и вам нравится функция indent в Python3.9, почему бы просто не взять dump функцию из 3.9 и реализовать ее в своем проекте? Исходный код находится здесь: https://github.com/python/cpython/blob/e56d54e447694c6ced2093d2273c3e3d60b36b6f/Lib/ast.py#L111-L175

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

Ответ №2:

У меня был один вариант использования, в котором я не мог перейти на Python 3.9 (где был добавлен аргумент indent ), но мне нужен был способ улучшить результат ast.dump .

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

 def prettify(ast_tree_str, indent=4):
    ret = []
    stack = []
    in_string = False
    curr_indent = 0

    for i in range(len(ast_tree_str)):
        char = ast_tree_str[i]
        if in_string and char != ''' and char != '"':
            ret.append(char)
        elif char == '(' or char == '[':
            ret.append(char)

            if i < len(ast_tree_str) - 1:
                next_char = ast_tree_str[i 1]
                if next_char == ')' or next_char == ']':
                    curr_indent  = indent
                    stack.append(char)
                    continue

            print(''.join(ret))
            ret.clear()

            curr_indent  = indent
            ret.append(' ' * curr_indent)
            stack.append(char)
        elif char == ',':
            ret.append(char)

            print(''.join(ret))
            ret.clear()
            ret.append(' ' * curr_indent)
        elif char == ')' or char == ']':
            ret.append(char)
            curr_indent -= indent
            stack.pop()
        elif char == ''' or char == '"':

            if (len(ret) > 0 and ret[-1] == '\') or (in_string and stack[-1] != char):
                ret.append(char)
                continue

            if len(stack) > 0 and stack[-1] == char:
                ret.append(char)
                in_string = False
                stack.pop()
                continue

            in_string = True
            ret.append(char)
            stack.append(char)
        elif char == ' ':
            pass
        else:
            ret.append(char)

    print(''.join(ret))

 

Использование:

 if __name__ == '__main__':
    content = """
@testdecorator
def my_method(a, b):
    def ola():
        print("Hello")
    ola()
    return (a   b) * 5   "dasdas,da'sda\'asdas\'\'"
    """

    ast_tree = ast.parse(source=content)
    prettify(ast.dump(ast_tree))
 

PS.: Это не на 100% эквивалентно тому, что можно было бы извлечь из Python 3.9 ast.dump(...., indent=n) , но на данный момент этого должно быть достаточно. Не стесняйтесь улучшать его