Python 解析一个.py文件,读取AST,修改它,然后写回修改后的源代码

Python 解析一个.py文件,读取AST,修改它,然后写回修改后的源代码,python,compiler-construction,abstract-syntax-tree,Python,Compiler Construction,Abstract Syntax Tree,我想以编程方式编辑python源代码。基本上,我想读取一个.py文件,生成,然后写回修改过的python源代码(即另一个.py文件) 有一些方法可以使用标准python模块(如或)解析/编译python源代码。但是,我认为它们都不支持修改源代码(例如,删除此函数声明)然后写回修改python源代码的方法 更新:我想这样做的原因是我想为python编写一个脚本,主要是通过删除语句/表达式、重新运行测试和查看中断的内容。您可能不需要重新生成源代码。当然,这对我来说有点危险,因为你还没有解释为什么你认

我想以编程方式编辑python源代码。基本上,我想读取一个
.py
文件,生成,然后写回修改过的python源代码(即另一个
.py
文件)

有一些方法可以使用标准python模块(如或)解析/编译python源代码。但是,我认为它们都不支持修改源代码(例如,删除此函数声明)然后写回修改python源代码的方法


更新:我想这样做的原因是我想为python编写一个脚本,主要是通过删除语句/表达式、重新运行测试和查看中断的内容。

您可能不需要重新生成源代码。当然,这对我来说有点危险,因为你还没有解释为什么你认为你需要生成一个充满代码的.py文件;但是:

  • 如果您想生成一个人们实际使用的.py文件,也许这样他们就可以填写表单并获得一个有用的.py文件插入到他们的项目中,那么您不想将其更改为AST并返回,因为这样会丢失所有格式(想想把Python如此可读的空白行,通过将相关的行分组在一起)()注释。相反,你可能想要使用模板引擎(例如,设计成使模板甚至文本文件更容易)来定制.pyfile,或者使用Rick Copeland的扩展。

  • 如果您试图在编译模块的过程中进行更改,请注意,您不必一直返回到文本;您可以直接编译AST,而不是将其返回到.py文件中

  • 但几乎在任何情况下,您都可能试图做一些动态的事情,像Python这样的语言实际上使之非常容易,而不需要编写新的.py文件!如果您扩展您的问题,让我们知道您真正想要完成什么,那么新的.py文件可能根本不会涉及到答案;我已经看到了数百个Pyth文件在做数百件真实世界事情的项目中,没有一个项目需要编写.py文件。因此,我必须承认,我有点怀疑您是否找到了第一个好的用例。:-)

更新:既然你已经解释了你要做的事情,我还是想在AST上操作一下。您将希望通过删除而不是删除文件中的行(这可能会导致半个语句因语法错误而消失),而是删除整个语句来进行变异—还有什么地方比在AST中更好呢?

对它自动生成的测试用例执行此操作,就像用于python 2.6的工具一样(它将Python2.x源代码转换为Python3.x源代码)

这两个工具都使用了这个库,它是python解析器/编译器机制的一个实现,当从source->AST->source往返时,可以在source中保留注释

如果您想进行更多类似于转换的重构,则可以满足您的需要

模块是您的另一个选项,并且(使用解析器模块)。但是当对随后转换为代码对象的代码执行ast转换时,
ast
模块更有用


该项目可能也很适合(ht Xavier Combelle)

内置ast模块似乎没有一种方法可以转换回源代码。但是,这里的模块为ast提供了一个漂亮的打印机,可以让您这样做。 例如

这将打印:

def foo():
    return 42
请注意,您可能会丢失确切的格式和注释,因为它们不会被保留

但是,您可能不需要这样做。如果您只需要执行替换的AST,只需在AST上调用compile(),并执行生成的代码对象即可。A是一种分析源文本、构建AST的工具,允许您使用源到源转换来修改源文本(“如果你看到这个模式,用那个模式替换它”)。这样的工具非常适合于对现有的源代码进行变异,这些源代码只是“如果你看到这个模式,用一个模式变体替换”

当然,您需要一个程序转换引擎,它可以解析您感兴趣的语言,并且仍然可以执行模式导向的转换。我们的系统可以做到这一点,并处理Python和其他各种语言

准确地看到这一点。DMS可以对AST进行更改,并重新生成有效文本,包括注释。您可以要求它使用自己的格式约定(您可以更改这些约定)预打印AST,或者执行“逼真打印”,即使用原始行和列信息最大限度地保留原始布局(在插入新代码的布局中不可避免地会发生一些变化)

要使用DMS实现Python的“变异”规则,可以编写以下代码:

rule mutate_addition(s:sum, p:product):sum->sum =
  " \s + \p " -> " \s - \p"
 if mutate_this_place(s);
这条规则以语法正确的方式将“+”替换为“-”;它在AST上运行,因此不会触及碰巧看起来正确的字符串或注释。“mutate_This_place”的额外条件是让您控制这种情况发生的频率;您不希望对程序中的每个位置都进行变异


很明显,您需要更多这样的规则来检测各种代码结构,并用变异版本替换它们。DMS很乐意应用一组规则。变异后的AST随后被预打印。

我最近创建的代码结构非常稳定(核心经过了很好的测试)以及从
ast
树生成代码的可扩展代码段:

我正在使用我的项目作为一个小型vim插件(我每天都在使用)的基础,所以我的目标是生成非常好的可读python代码

附言。 我试图扩展
codegen
,但它的体系结构基于
ast.NodeVisitor
接口,所以格式化程序(
visitor\uu
方法)只是函数。我发现这种结构非常有限,很难优化(在长时间和长时间的情况下)
rule mutate_addition(s:sum, p:product):sum->sum =
  " \s + \p " -> " \s - \p"
 if mutate_this_place(s);
pip install git+https://github.com/berkerpeksag/astor.git#egg=astor
>>> import ast
>>> import astor
>>> print(astor.to_source(ast.parse('def foo(x): return 2 * x')))
def foo(x):
    return 2 * x
>>> import ast
>>> import astunparse
>>> print(astunparse.unparse(ast.parse('def foo(x): return 2 * x')))


def foo(x):
    return (2 * x)
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
>>> import ast
>>> tree = ast.parse("print 'Hello Python!!'")
>>> exec(compile(tree, filename="<ast>", mode="exec"))
Hello Python!!
>>> import ast
>>> tree = ast.parse("print 'Hello Python!!'")
>>> ast.dump(tree)
"Module(body=[Print(dest=None, values=[Str(s='Hello Python!!')], nl=True)])"
>>> 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=[]))])"
#!/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])
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.unparse(ast_obj)