parsing.py文件,读取AST,修改它,然后回写修改的源代码

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

有很多方法可以使用标准的python模块(如astcompiler来parsing/编译python源代码。 但是,我不认为他们中的任何一个都支持修改源代码的方法(例如,删除这个函数声明),然后回写修改的python源代码。

更新:我想这样做的原因是我想写一个Python的突变testing库 ,主要是通过删除语句/expression式,重新运行testing,看看什么样的rest。

Pythoscope做到这一点,它自动生成的testing用例,如Python 2的2to3工具(它将Python 2.x源代码转换为Python 3.x源代码)。

这两个工具都使用lib2to3库,它是一个Pythonparsing器/编译器机制的实现,它可以在从源代码 – > AST – >源代码中跳过时保留源代码中的注释。

绳子项目可以满足你的需求,如果你想做更多的重构像变换。

ast模块是您的另一种select, 还有一个较早的例子,就是如何将语法树“parsing”回代码 (使用parsing器模块)。 但是,对代码进行AST转换,然后转换为代码对象时,ast模块更有用。

Redbaron项目也可能非常适合(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 

请注意,您可能会丢失确切的格式和注释,因为这些不会保留。

但是,您可能不需要。 如果你所需要的只是执行被replace的AST,你可以简单地通过调用ast的compile()来完成,并且生成代码对象。

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

  • 如果你想生成一个人们实际使用的.py文件,也许这样他们可以填写一个表单,并获得一个有用的.py文件插入到他们的项目,那么你不想把它改变成一个AST和因为你将失去所有的格式(想想通过将相关的行集合在一起使Python可读的空行) ( ast节点具有linenocol_offset属性 )注释。 相反,您可能需要使用模板引擎(例如, Django模板语言 ,旨在使模板甚至文本文件更容易)来自定义.py文件,或者使用Rick Copeland的MetaPython扩展。

  • 如果您在编译模块期间尝试进行更改,请注意您不必一直回到文本; 您可以直接编译AST而不是将其转换回.py文件。

  • 但几乎在任何情况下,您都可能试图做一些dynamic的事情,像Python这样的语言实际上变得非常简单,而无需编写新的.py文件! 如果你扩大你的问题,让我们知道你真的想要完成,新的.py文件可能不会涉及到答案, 我已经看到数以百计的Python项目正在执行数百个真实世界的事情,而不需要编写一个.py文件。 所以,我必须承认,我有点怀疑你已经find了第一个好的用例。 🙂

更新:现在你已经解释了你正在尝试做的事情,但我仍然试图在AST上进行操作。 你会想要通过删除而不是一行文件(这可能会导致只有一个SyntaxError而死的半语句),但是整个语句 – 还有比AST更好的地方呢?

在另一个答案中,我build议使用astor包,但是我已经find了一个更新的ASTparsing包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上testing了这个。

我最近创build了相当稳定(核心是真正的testing)和可扩展的一段代码,从ast树生成代码: https : //github.com/paluh/code-formatter 。

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

PS我试图扩展ast.NodeVisitor ,但它的架构是基于ast.NodeVisitor接口,所以格式化( visitor_方法)只是function。 我发现这个结构是相当有限的,很难优化(在长expression式和嵌套expression式的情况下,更容易保持对象树并caching一些部分结果 – 换句话说,如果要search最佳布局,则可以达到指数复杂度)。 但代码作为每一个mitsuhiko的作品(我读过)是非常好的写作和简洁。

ast模块的帮助下parsing和修改代码结构当然是可能的,我将在一个例子中展示它。 但是,只有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!! 

您可以通过简单地调用API ast.parse()来parsingpython代码(以string表示ast.parse() 。 这将把句柄返回到抽象语法树(AST)结构。 有趣的是,你可以编译回这个结构并执行它,如上所示。

另一个非常有用的API是ast.dump() ,它以stringforms转储整个AST。 它可以用来检查树结构,对debugging非常有帮助。 例如,

在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中的print语句与Python 3.5中的print语法的区别,以及相应树中AST节点types的区别。


如何使用ast修改代码:

现在,让我们来看看一个由ast模块修改python代码的例子。 修改AST结构的主要工具是ast.NodeTransformer类。 无论何时需要修改AST,他/她都需要从中进行子类化,并相应地写入节点转换。

在我们的例子中,我们来编写一个简单的实用程序,它将Python 2,打印语句转换为Python 3函数调用。

打印语句到有趣的电话转换器实用程序: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]) 

这个实用程序可以在小例子文件上尝试,比如下面的文件,它应该可以正常工作。

testinginput文件: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")

我们有类似的需求,这是其他答案没有解决。 所以我们为此创build了一个库ASTTokens ,它采用ast或astroid模块生成的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 

希望这可以帮助!

程序转换系统是一个parsing源文本,构buildAST的工具,允许你使用源到源的转换(如果你看到这个模式,用这种模式replace它)来修改它们。 这样的工具非常适合对现有源代码进行变异,这些代码只是“如果你看到这种模式,用模式变体代替”。

当然,你需要一个程序转换引擎,可以parsing你感兴趣的语言,并且仍然执行模式转换。 我们的DMS Software Reengineering Toolkit是一个可以做到这一点的系统,可以处理Python和其他各种语言。

看到这个答案的DMSparsing的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); 

这条规则用“ – ”以句法正确的方式replace“+”。 它在AST上运行,因此不会碰到看起来正确的string或注释。 “mutate_this_place”的额外条件是让你控制这种情况发生的频率。 你不想改变程序中的每一个地方。

你显然希望有更多像这样的规则来检测各种代码结构,并将其replace为变体版本。 DMS很乐意应用一套规则。 然后突变的AST被打印出来。

其他答案之一推荐codegen ,似乎已被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上testing了这个。