Проаналізуйте файл .py, прочитайте AST, змініть його, а потім запишіть змінений вихідний код


168

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

Існують способи розбору / компіляції вихідного коду python за допомогою стандартних модулів python, таких як astабоcompiler . Однак я не думаю, що жоден з них підтримує способи зміни вихідного коду (наприклад, видалити цю функцію декларації), а потім записати зміну вихідного коду python.

ОНОВЛЕННЯ: Причиною цього я хочу зробити те, що я хотів би написати бібліотеку тестування мутацій для python, здебільшого шляхом видалення висловлювань / виразів, повторних тестів і бачити, що порушується.


4
Застаріло з версії 2.6: Пакет компілятора видалено в Python 3.0.
dfa

1
Що ви не можете редагувати джерело? Чому ви не можете написати декоратора?
S.Lott

3
Свята корова! Я хотів зробити тестер мутації для python за тією ж методикою (спеціально створивши плагін для носа), чи плануєте ви його відкритими джерелами?
Райан

2
@Ryan Так, я відкрию джерело всього, що я створюю. Ми повинні підтримувати зв’язок з цього приводу
Рорі

1
Безумовно, я надіслав вам електронний лист через Launchpad.
Райан

Відповіді:


73

Пітоскоп робить це для тестових випадків, які він автоматично генерує, як і 2to3 інструмент для python 2.6 (він перетворює джерело python 2.x у джерело python 3.x).

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

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

Модуль ast - це ваш інший варіант, і є більш старий приклад того, як "розпакувати" синтаксичні дерева назад у код (за допомогою модуля аналізатора). Але astмодуль корисніший при здійсненні перетворення AST на код, який потім перетворюється на об'єкт коду.

Проект redbaron також може бути гарним підходом (ht Xavier Combelle)


5
приклад нерозбірливості досі зберігається, ось оновлена ​​версія py3k
Janus Troelsen

2
Що стосується unparse.pyскрипту - використовувати його можна з іншого сценарію. Але є пакунок під назвою astunparse ( на github , на pypi ), який в основному є правильно упакованою версією unparse.py.
mbdevpl

Не могли б ви оновити свою відповідь, додавши парсо як кращий варіант? Це дуже добре і оновлено.
бокс

59

Вбудований модуль ast, схоже, не має методу перетворення назад до джерела. Однак кодеген модуль тут забезпечує прекрасний принтер для 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 і виконуючи отриманий кодовий об'єкт.


20
Просто для тих, хто використовує це в майбутньому, кодеген значною мірою застарілий і має кілька помилок. Я зафіксував пару з них; Я маю це як суть на github: gist.github.com/791312
mattbasta

Зауважте, останній кодеген оновлюється в 2012 році, після закінчення вищевказаного коментаря, тому, мабуть, кодеген оновлений. @mattbasta
zjffdu

4
Астор, схоже, є збереженим спадкоємцем кодегену
medmunds

20

У іншій відповіді я запропонував використовувати astorпакет, але з тих пір я знайшов більш сучасний пакет AST для розбору, який називається 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.


19

Можливо, вам не знадобиться повторно генерувати вихідний код. Для мене це, звичайно, трохи небезпечно, оскільки ви насправді не пояснили, чому ви думаєте, що вам потрібно створити .py файл з кодом; але:

  • Якщо ви хочете створити .py-файл, який люди фактично використовуватимуть, можливо, щоб вони могли заповнити форму та отримати корисний .py-файл, який потрібно вставити у свій проект, тоді ви не хочете змінювати його на AST та тому , тому що ви втратите всі форматування (згадає порожні рядки , які роблять Python так читаються угруповання пов'язаних наборів ліній разом) ( AST вузли мають linenoі col_offsetатрибути ) коментарі. Натомість ви, ймовірно, захочете використовувати механізм шаблонів ( наприклад, мова шаблонів Django призначена для спрощення шаблону навіть текстових файлів) для налаштування .py-файлу або використання розширення MetaPython Rick Copeland .

  • Якщо ви намагаєтесь внести зміни під час компіляції модуля, зауважте, що вам не доведеться повертатися до тексту; ви можете просто скласти AST безпосередньо, а не повертати його назад у файл .py.

  • Але майже в будь-якому випадку ви, мабуть, намагаєтесь зробити щось динамічне, що мова на зразок Python насправді робить дуже простою, без написання нових .py-файлів! Якщо ви розгорнете своє запитання, щоб повідомити нам, що ви насправді хочете досягти, нові .py файли, ймовірно, взагалі не будуть задіяні у відповіді; Я бачив сотні проектів Python, які роблять сотні реальних речей, і жоден з них не потрібен, щоб коли-небудь написати файл .py. Тож, я мушу визнати, я трохи скептичний, що ти знайшов перший добрий випадок. :-)

Оновлення: тепер, коли ви пояснили, що ви намагаєтеся зробити, я б спокусився просто діяти на AST. Ви хочете мутувати, видаляючи не рядки файлу (що може призвести до напіввисловлювань, які просто вмирають за допомогою SyntaxError), а цілі заяви - і що краще зробити для цього, ніж в AST?


Хороший огляд можливого рішення та ймовірних альтернатив.
Райан

1
У реальному світі використовується приклад для генерації коду: Kid та Genshi (я вважаю) генерують Python із шаблонів XML для швидкого відображення динамічних сторінок.
Рік Коупленд

10

Розбір і зміна структури коду, безумовно, можливий за допомогою astмодуля, і я покажу його на прикладі за мить. Однак повернення зміненого вихідного коду неможливе лише з astмодулем. Для цієї роботи доступні інші модулі, такі як один тут .

ПРИМІТКА: Приклад нижче може розглядатися як вступний підручник щодо використання astмодуля, але більш повне керівництво по використанню astмодуля доступне тут у навчальному посібнику щодо змій Green Tree та офіційній документації на 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, друкує заяви на виклики функції Python 3.

Роздрукувати заяву до утиліти для перетворення викликів Fun: 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").


6

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

Я використовую свій проект як основу для невеликого плагіну vim (який я використовую щодня), тому моя мета - генерувати дійсно приємний і читабельний код python.

PS Я намагався розширити, codegenале архітектура базується на ast.NodeVisitorінтерфейсі, тому формати ( visitor_методи) - це лише функції. Я знайшов цю структуру досить обмежувальною і важко оптимізувати (у разі довгих і вкладених виразів легше зберігати дерево об’єктів і кешувати деякі часткові результати - інакше ви можете досягти експоненціальної складності, якщо хочете шукати найкращий макет). Але, codegen як і кожен твір міцухіко (який я читав) дуже добре написано і стисло.


4

Один з інших відповідей рекомендує 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.


4

Якщо ви дивитесь на це у 2019 році, то можете скористатися цим libcst пакетом . Він має синтаксис, подібний до аст. Це працює як шарм і зберігає структуру коду. Це в основному корисно для проекту, де вам потрібно зберегти коментарі, пробіли, новий рядок тощо.

Якщо вам не потрібно дбати про збереження коментарів, пробілів та інших, то поєднання аст та астора працює добре.


2

У нас була подібна потреба, яку не вирішили тут інші відповіді. Таким чином , ми створили бібліотеку для цього, 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

Сподіваюся, це допомагає!


1

Система трансформації програми - це інструмент, який аналізує вихідний текст, створює AST, дозволяє змінювати їх, використовуючи перетворення від джерела до джерела ("якщо ви бачите цей зразок, замініть його на цей шаблон"). Такі інструменти ідеально підходять для мутації існуючих вихідних кодів, які просто "якщо ви бачите цю схему, замініть на варіант шаблону".

Звичайно, вам потрібен механізм перетворення програм, який може розбирати цікаву для вас мову, і все ж робити перетворення, спрямовані на шаблон. Наш інструментарій для реінжинірингу програмного забезпечення DMS - це система, яка може це зробити, і обробляє Python та різноманітні інші мови.

Дивіться цю відповідь ТА на прикладі AST-розбору AST для Python, що чітко фіксує коментарі . DMS може вносити зміни до AST та відновлювати дійсний текст, включаючи коментарі. Ви можете попросити його досить надрукувати 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 із задоволенням застосовує набір правил. Після цього мутований AST досить друкується.


Я не дивився на цю відповідь 4 роки. Нічого собі, це було знято кілька разів. Це насправді приголомшливо, оскільки він відповідає безпосередньо на питання ОП і навіть показує, як робити мутації, які він хоче робити. Я не думаю, що хтось із прихильників не піклується про те, щоб пояснити, чому вони виступали.
Іра Бакстер

4
Тому що він рекламує дуже дорогий інструмент із закритим кодом.
Зоран Павлович

@ZoranPavlovic: Отже, ви не заперечуєте проти жодної його технічної точності чи корисності?
Іра Бакстер

2
@Zoran: Він не сказав, що має бібліотеку з відкритим кодом. Він сказав, що хоче змінити вихідний код Python (використовуючи AST), і рішення, які він міг знайти, цього не робили. Це таке рішення. Ви не думаєте, що люди використовують комерційні засоби для програм, написаних такими мовами, як Python на Java?
Іра Бакстер

1
Я не переможець, але пост читається трохи як реклама. Щоб покращити відповідь, ви могли б виявити, що ви пов’язані з продуктом
wim

0

Раніше я використовував барон для цього, але тепер перейшов на парсо, оскільки це в курсі сучасного пітона. Це чудово працює.

Мені це знадобилося і для тестеру на мутації. Це зробити дуже просто з parso, перевірити мій код на https://github.com/boxed/mutmut

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.