[python] Разберите файл .py, прочитайте AST, измените его, а затем запишите измененный исходный код


Answers

У встроенного модуля ast нет способа конвертировать обратно в исходный код. Тем не менее, модуль codegen здесь предоставляет красивый принтер для астра, который позволит вам сделать это. например.

import ast
import codegen

expr="""
def foo():
   print("hello world")
"""
p=ast.parse(expr)

p.body[0].body = [ ast.parse("return 42").body[0] ] # Replace function body with "return 42"

print(codegen.to_source(p))

Это напечатает:

def foo():
    return 42

Обратите внимание, что вы можете потерять точное форматирование и комментарии, так как они не сохраняются.

Однако вам может и не понадобиться. Если все, что вам требуется, это выполнить замененный АСТ, вы можете сделать это просто, вызвав compile () на ast и выполнив полученный объект кода.

Question

Я хочу программно редактировать исходный код python. В основном я хочу прочитать .py файл, сгенерировать AST , а затем записать измененный исходный код python (т. .py Другой .py файл).

Есть способы проанализировать / скомпилировать исходный код python с использованием стандартных модулей python, таких как ast или compiler . Тем не менее, я не думаю, что любой из них поддерживает способы изменения исходного кода (например, удалить это объявление функции), а затем записать обратно модифицирующий исходный код python.

ОБНОВЛЕНИЕ. Причина, по которой я хочу это сделать, - это написать библиотеку тестирования мутаций для python, в основном путем удаления операторов / выражений, повторных тестов и просмотра разрывов.




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

ПРИМЕЧАНИЕ. Пример ниже можно рассматривать как вводный учебник по использованию модуля ast но более подробное руководство по использованию модуля ast доступно здесь, в учебнике Green Tree snakes и официальной документации по ast модулю .

Введение в ast :

>>> import ast
>>> tree = ast.parse("print 'Hello Python!!'")
>>> exec(compile(tree, filename="<ast>", mode="exec"))
Hello Python!!

Вы можете разобрать код python (представленный в строке), просто называя API ast.parse() . Это возвращает дескриптор структуры абстрактного синтаксического дерева (AST). Интересно, что вы можете скомпилировать эту структуру и выполнить ее, как показано выше.

Другим очень полезным API является ast.dump() который выгружает весь AST в строковой форме. Его можно использовать для проверки древовидной структуры и очень полезно при отладке. Например,

На Python 2.7:

>>> import ast
>>> tree = ast.parse("print 'Hello Python!!'")
>>> ast.dump(tree)
"Module(body=[Print(dest=None, values=[Str(s='Hello Python!!')], nl=True)])"

На Python 3.5:

>>> import ast
>>> tree = ast.parse("print ('Hello Python!!')")
>>> ast.dump(tree)
"Module(body=[Expr(value=Call(func=Name(id='print', ctx=Load()), args=[Str(s='Hello Python!!')], keywords=[]))])"

Обратите внимание на разницу в синтаксисе для оператора печати в Python 2.7 по сравнению с Python 3.5 и разницу в типе узла AST в соответствующих деревьях.

Как изменить код с помощью ast :

Теперь давайте рассмотрим пример модификации кода python с помощью модуля ast . Основным инструментом для модификации структуры AST является класс ast.NodeTransformer . Всякий раз, когда нужно модифицировать AST, ему / ей необходимо подклассу из него и соответствующим образом изменять Преобразование узлов.

В нашем примере давайте попробуем написать простую утилиту, которая преобразует операторы печати Python 2, print в вызовы функций Python 3.

Заявление на печать в утилите конвертера Fun call: print2to3.py:

#!/usr/bin/env python
'''
This utility converts the python (2.7) statements to Python 3 alike function calls before running the code.

USAGE:
     python print2to3.py <filename>
'''
import ast
import sys

class P2to3(ast.NodeTransformer):
    def visit_Print(self, node):
        new_node = ast.Expr(value=ast.Call(func=ast.Name(id='print', ctx=ast.Load()),
            args=node.values,
            keywords=[], starargs=None, kwargs=None))
        ast.copy_location(new_node, node)
        return new_node

def main(filename=None):
    if not filename:
        return

    with open(filename, 'r') as fp:
        data = fp.readlines()
    data = ''.join(data)
    tree = ast.parse(data)

    print "Converting python 2 print statements to Python 3 function calls"
    print "-" * 35
    P2to3().visit(tree)
    ast.fix_missing_locations(tree)
    # print ast.dump(tree)

    exec(compile(tree, filename="p23", mode="exec"))

if __name__ == '__main__':
    if len(sys.argv) <=1:
        print ("\nUSAGE:\n\t print2to3.py <filename>")
        sys.exit(1)
    else:
        main(sys.argv[1])

Эта утилита может быть опробована в небольшом файле примера, например, ниже, и она должна работать нормально.

Тестовый входной файл: py2.py

class A(object):
    def __init__(self):
        pass

def good():
    print "I am good"

main = good

if __name__ == '__main__':
    print "I am in main"
    main()

Обратите внимание, что преобразование выше только для целей учебного курса, и в реальном случае придется искать все различные сценарии, такие как print " x is %s" % ("Hello Python") .




В другом ответе я предложил использовать пакет astor , но с тех пор я нашел более современный пакет Un-parsing astunparse названный astunparse :

>>> import ast
>>> import astunparse
>>> print(astunparse.unparse(ast.parse('def foo(x): return 2 * x')))


def foo(x):
    return (2 * x)

Я тестировал это на Python 3.5.




Система преобразования программ - это инструмент, который анализирует исходный текст, создает АСТ, позволяет изменять их с использованием преобразований источника в источник («если вы видите этот шаблон, замените его на этот шаблон»). Такие инструменты идеально подходят для мутации существующих исходных кодов, которые просто «если вы видите этот шаблон, замените его на вариант шаблона».

Конечно, вам нужен механизм преобразования программ, который может анализировать интересующий вас язык и все еще делать преобразования, управляемые шаблонами. Наш DMS Software Reengineering Toolkit - это система, которая может это сделать, и обрабатывает Python и множество других языков.

См. Этот SO-ответ на примере анализируемого DMS-анализа для Python, который точно фиксирует комментарии . DMS может вносить изменения в AST и восстанавливать действующий текст, включая комментарии. Вы можете попросить его распечатать AST, используя свои собственные соглашения о форматировании (вы можете их изменить) или сделать «верность печати», которая использует исходную информацию о строках и столбцах, чтобы максимально сохранить исходный макет (некоторые изменения в макете, где новый код вставляется неизбежно).

Чтобы реализовать правило «мутации» для Python с DMS, вы можете написать следующее:

rule mutate_addition(s:sum, p:product):sum->sum =
  " \s + \p " -> " \s - \p"
 if mutate_this_place(s);

Это правило заменяет «+» на «-» синтаксически правильным образом; он действует на АСТ и, таким образом, не будет касаться строк или комментариев, которые выглядят правильно. Дополнительное условие «mutate_this_place» - позволить вам контролировать, как часто это происходит; вы не хотите мутировать каждое место в программе.

Очевидно, вам понадобится еще больше таких правил, которые обнаруживают различные структуры кода и заменяют их мутированными версиями. DMS рада применить набор правил. Мутированный АСТ затем красиво печатается.




Related