我想以编程方式编辑python源代码。基本上我想读取一个.py文件,生成AST,然后写回修改后的python源代码(即另一个.py文件)。
有一些方法可以使用标准的python模块(如ast或compiler)来解析/编译python源代码。但是,我认为它们都不支持修改源代码的方法(例如删除这个函数声明),然后再写回修改的python源代码。
更新:我想这样做的原因是我想为python写一个突变测试库,主要是通过删除语句/表达式,重新运行测试,看看有什么破坏。
我想以编程方式编辑python源代码。基本上我想读取一个.py文件,生成AST,然后写回修改后的python源代码(即另一个.py文件)。
有一些方法可以使用标准的python模块(如ast或compiler)来解析/编译python源代码。但是,我认为它们都不支持修改源代码的方法(例如删除这个函数声明),然后再写回修改的python源代码。
更新:我想这样做的原因是我想为python写一个突变测试库,主要是通过删除语句/表达式,重新运行测试,看看有什么破坏。
当前回答
内置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()并执行结果代码对象即可。
其他回答
另一种回答建议使用密码原,它似乎已被阿斯特取代。PyPI上的astor版本(撰写本文时的版本为0.5)似乎也有点过时,因此您可以按如下方式安装astor的开发版本。
pip install git+https://github.com/berkerpeksag/astor.git#egg=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上进行了测试。
内置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()并执行结果代码对象即可。
我以前使用baron,但现在已经切换到parso,因为它是现代python的最新版本。效果很好。
我还需要这个做变异测试。用parso做一个真的很简单,请访问https://github.com/boxed/mutmut查看我的代码
Pythoscope对它自动生成的测试用例执行此操作,就像python 2.6的2to3工具一样(它转换python 2。X源代码转换为python 3。x源)。
这两个工具都使用lib2to3库,它是python解析器/编译器机制的实现,可以在从source -> AST -> source循环绊倒源代码时保留源代码中的注释。
如果您想进行更多的重构(如转换),rope项目可以满足您的需求。
ast模块是另一个选择,还有一个关于如何将语法树“解解析”回代码的旧示例(使用解析器模块)。但是ast模块在对随后转换为代码对象的代码进行ast转换时更有用。
红男爵计划也可能是个不错的选择(泽维尔·康贝尔)
在ast模块的帮助下,解析和修改代码结构当然是可能的,我将在稍后的示例中展示它。然而,仅使用ast模块是不可能写回修改后的源代码的。还有其他模块可用于这项工作,例如这里的一个。
注意:下面的例子可以作为ast模块使用的入门教程,但是更全面的ast模块使用指南可以在绿树蛇教程和ast模块的官方文档中找到。
ast简介:
>>> import ast
>>> tree = ast.parse("print 'Hello Python!!'")
>>> exec(compile(tree, filename="<ast>", mode="exec"))
Hello Python!!
你可以通过调用API ast.parse()来解析python代码(以字符串表示)。它返回抽象语法树(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类。每当一个人需要修改AST时,他/她需要从它继承子类并相应地编写节点转换。
对于我们的例子,让我们试着编写一个简单的实用程序,它可以将python2, print语句转换为python3函数调用。
打印语句到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()
请注意,上面的转换仅供上一个教程使用,在实际情况下,必须查看所有不同的场景,例如打印“x是%s”%(“Hello Python”)。