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


Я хочу программно редактировать исходный код python. В основном я хочу прочитать создавать АСТ, а затем записать обратно измененный исходный код python (т. е. другой .py file).

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

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

10 137

10 ответов:

Pythoscope это тестовый случай автоматически генерирует как 2to3 инструмент для python 2.6 (он преобразует python 2.X источник в python 3.х источник).

оба эти инструмента используют lib2to3 библиотека, которая является реализацией механизма синтаксического анализа/компилятора python, который может сохранять комментарии в источнике, когда он круглый отключен от источника -> AST -> source.

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

на АСТ модуль-это другой вариант, и есть более старый пример того, как" unparse " синтаксические деревья обратно в код (С помощью модуля парсера). Но это ast модуль более полезен при выполнении преобразования AST на коде, который затем преобразуется в объект кода.

на redbaron проект также может быть хорошо подходит (ht Xavier Combelle)

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

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

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

однако, возможно, Вам это не понадобится. Если все, что вам нужно, это выполнить замененный AST, вы можете сделать это просто вызов compile () на ast и выполнение результирующего объекта кода.

возможно, Вам не нужно заново генерировать исходный код. Это немного опасно для меня, конечно, так как вы на самом деле не объяснили, почему вы думаете, что вам нужно создать файл .py, полный кода; но:

  • Если вы хотите создать файл .py, который люди будут фактически использовать, возможно, чтобы они могли заполнить форму и получить полезный файл .py для вставки в свой проект, то вы не хотите менять его на AST и обратно, потому что вы потеряете все форматирование (подумайте о пустых строках, которые делают Python настолько читаемым, группируя связанные наборы строк вместе) (узлы ast имеют lineno и col_offset атрибуты) замечания. Вместо этого вы, вероятно, захотите использовать механизм шаблонов (язык шаблонов Django, например, предназначен для упрощения шаблонов даже текстовых файлов), чтобы настроить файл. py, или же использовать Rick Copeland в MetaPython

в другом ответе я предложил использовать astor пакет, но с тех пор я нашел более современный пакет AST un-parsing под названием 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.

Я недавно создал довольно стабильный (ядро действительно хорошо протестировано) и расширяемый фрагмент кода, который генерирует код из ast дерево:https://github.com/paluh/code-formatter .

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

С. П. Я пытался расширить codegen но это архитектура, основанная на ast.NodeVisitor интерфейс, так форматеры (visitor_ методы) - это просто функции. Я нашел эту структуру довольно ограниченной и трудной для оптимизации (в случае длинных и вложенных выражений легче сохранить дерево объектов и кэшировать некоторые частичные результаты - иначе вы можете попасть в экспоненциальную сложность, если хотите найти лучший макет). ноcodegen так как каждая часть работы мицухико (которую я читал) очень хорошо написана и лаконична.

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

Примечание: пример ниже можно рассматривать как вводный учебник по использованию ast модуль, но более подробное руководство по использованию ast модуль доступен здесь по адресу зеленый Дерево змей учебник и официальная документация о ast модуль.

введение ast:

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

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

другое очень полезные Иза 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=[]))])"

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


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

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

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

Print заявление в Fun Call converter утилита: 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()

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

один из других ответов рекомендует codegen, который, кажется, был заменен astor. Версия astor на PyPI (версия 0.5 на момент написания этой статьи), похоже, немного устарела, поэтому вы можете установить версию разработки astor следующим образом.

pip install git+https://github.com/berkerpeksag/astor.git#egg=astor

затем вы можете использовать astor.to_source чтобы преобразовать Python AST в читаемый человеком исходный код Python:

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

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

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

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

посмотреть этот так ответьте на пример DMS-parsed AST для Python capturing comments точно. DMS может вносить изменения в AST и восстанавливать допустимый текст, включая комментарии. Вы можете попросить его prettyprint AST, используя свои собственные соглашения о форматировании (вы можете изменить их), или сделайте "печать точности", которая использует исходную информацию о строке и столбце, чтобы максимально сохранить исходный макет (некоторые изменения в макете, где вставляется новый код, неизбежны).

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

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

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

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

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

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

например, это обертывает вызов функции в WRAP(...), сохраняя комментарии и все остальное:

example = """
def foo(): # Test
  '''My func'''
  log("hello world")  # Print
"""

import ast, asttokens
atok = asttokens.ASTTokens(example, parse=True)

call = next(n for n in ast.walk(atok.tree) if isinstance(n, ast.Call))
start, end = atok.get_text_range(call)
print(atok.text[:start] + ('WRAP(%s)' % atok.text[start:end])  + atok.text[end:])

выдает:

def foo(): # Test
  '''My func'''
  WRAP(log("hello world"))  # Print

надеюсь, что это помогает!

раньше я использовал baron для этого, но теперь переключился на parso, потому что он обновлен с современным python. Он отлично работает.

Я также нуждался в этом для тестера мутаций. Это действительно довольно просто сделать один с парсо, проверить мой код наhttps://github.com/boxed/mutmut