我想以编程方式编辑python源代码.基本上我想读取一个.py
文件,生成AST,然后写回修改过的python源代码(即另一个.py
文件).
有一些方法可以使用标准的python模块解析/编译python源代码,例如ast
或compiler
.但是,我不认为它们中的任何一个都支持修改源代码的方法(例如删除此函数声明),然后回写修改python源代码.
更新:我想这样做的原因是我想为python 编写一个Mutation测试库,主要是通过删除语句/表达式,重新运行测试和查看什么中断.
Pythoscope对它自动生成的测试用例执行此操作,就像python 2.6 的2to3工具一样(它将python 2.x源转换为python 3.x源).
这两个工具都使用lib2to3库,它是python解析器/编译器机器的一个实现,当它从源 - > AST - >源进行往返时可以保留源代码中的注释.
如果您想进行更多重构,那么此项目可能会满足您的需求.
该AST模块是你的其他选择,并有一个旧例子如何"unparse"语法树回代码(使用解析器模块).但是ast
当对代码进行AST转换然后转换为代码对象时,该模块更有用.
该redbaron项目也可能是一个不错的选择(HT泽维尔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,则只需在ast上调用compile()并执行生成的代码对象即可.
您可能不需要重新生成源代码.当然,这对我来说有点危险,因为你实际上没有解释为什么你认为你需要生成一个充满代码的.py文件; 但:
如果你想生成一个人们会实际使用的.py文件,也许这样他们可以填写一个表单并获得一个有用的.py文件插入到他们的项目中,那么你不想把它改成AST和因为你将失去所有的格式(想想通过将相关的行集合在一起使得Python具有可读性的空行)(ast节点有lineno
和col_offset
属性)注释.相反,您可能希望使用模板引擎(例如,Django模板语言,旨在使模板甚至文本文件变得容易)来自定义.py文件,或者使用Rick Copeland的MetaPython扩展.
如果您在编译模块期间尝试进行更改,请注意您不必一直回到文本; 你可以直接编译AST而不是把它变回.py文件.
但几乎在任何情况下,你可能都试图做一些动态的东西,像Python这样的语言实际上很容易,而不用编写新的.py文件!如果你扩展你的问题让我们知道你真正想要完成什么,那么新的.py文件可能根本不会涉及答案; 我已经看到数百个Python项目正在处理数百个真实世界的东西,而且只需编写一个.py文件就不需要其中一个.所以,我必须承认,我有点怀疑你发现了第一个好的用例.:-)
更新:既然你已经解释了你想要做的事情,那么我很想在AST上进行操作.你会希望通过删除而不是文件的行来改变(这可能导致半语句只是死于SyntaxError),而是整个语句 - 还有什么比在AST中更好的地方呢?
在一个不同的答案我建议使用该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上测试了这个.
在ast
模块的帮助下,解析和修改代码结构当然是可能的,我稍后会在一个例子中展示它.但是,ast
单独使用模块无法写回修改后的源代码.此工作还有其他模块,例如此处的模块.
注意:下面的示例可以作为ast
模块使用的入门教程处理,但有关使用ast
模块的更全面的指南,请参阅Green Tree snakes教程和模块的官方文档ast
.
简介ast
:
>>> import ast >>> tree = ast.parse("print 'Hello Python!!'") >>> exec(compile(tree, filename="", mode="exec")) Hello Python!!
您只需调用API即可解析python代码(以字符串表示)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中print语句的语法差异以及各个树中AST节点类型的差异.
如何修改代码使用ast
:
现在,让我们来看一个按ast
模块修改python代码的例子.修改AST结构的主要工具是ast.NodeTransformer
class.每当需要修改AST时,他/她需要从中进行子类化并相应地编写节点转换.
对于我们的示例,让我们尝试编写一个简单的实用程序,它将Python 2,print语句转换为Python 3函数调用.
打印语句到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''' 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 ") 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")
.
我最近创建的非常稳定(核心经过了很好的测试)和可扩展的代码片段,它从ast
树生成代码:https://github.com/paluh/code-formatter.
我正在使用我的项目作为小vim插件的基础(我每天都在使用它),因此我的目标是生成非常好的和可读的python代码.
PS我试图扩展,codegen
但它的架构基于ast.NodeVisitor
接口,因此格式化程序(visitor_
方法)只是函数.我发现这种结构非常有限并且难以优化(在长和嵌套表达式的情况下,更容易保留对象树并缓存一些部分结果 - 换句话说,如果你想搜索最佳布局,你可以达到指数复杂性).但 codegen
由于每一块光彦的工作(这是我读过)是写得很好,简洁.