Pythoscope 会对其自动生成的测试用例进行此操作,Python 2.6 版本的 2to3 工具也会这样做(它将 Python 2.x 源代码转换为 Python 3.x 源代码)。
这两个工具都使用了 lib2to3 库,它是 Python 解析器/编译器机制的一种实现,可以在源代码往返转换时保留注释 -> AST -> 源代码。
如果你想进行更多的重构转换,可以考虑使用 rope 项目。
另外一个选择是使用 ast 模块,还有一个旧的示例来“反解析”语法树(使用 parser 模块)。但是 ast 模块更适用于对被转换为代码对象的 AST 进行变换。
还有一个名为 redbaron 项目(由 Xavier Combelle 提供的建议),它也可能非常适合你的需求。
unparse.py
脚本 - 从另一个脚本中使用它可能会非常麻烦。但是,有一个名为 astunparse 的包(在 github 上,在 pypi 上),它基本上是 unparse.py
的适当打包版本。 - mbdevpllib2to3
程序库似乎缺乏文档,尽管 http://python3porting.com/fixers.html 提供了一些注释。然而存在一个问题,API 被声称不稳定,并且正如 https://docs.python.org/3/library/2to3.html 中所解释的,该库无法解析某些 Python 3.10 语法并将很快被删除。 - user202729内置的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
请注意,您可能会失去精确的格式和注释,因为它们未被保留。花费了一些时间,但 Python 3.9 有这个功能:
https://docs.python.org/3.9/whatsnew/3.9.html#ast https://docs.python.org/3.9/library/ast.html#ast.unparseast.unparse(ast_obj)
将ast.AST对象反解析并生成一个字符串,该字符串可以使用ast.parse()解析后生成与原始ast.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上测试过此代码。
你可能不需要重新生成源代码。当然,我这么说有点危险,因为你并没有解释为什么需要生成一个充满代码的.py文件;但是:
如果你想生成一个实际使用的 .py 文件,比如让人们填写表单并获取有用的 .py 文件插入到他们的项目中,那么你不希望将其更改为 AST,然后再转换回来,因为你会失去注释以及所有格式(请考虑通过将相关的一组行分组在一起使 Python 变得易读的空白行)(ast 节点具有 lineno 和 col_offset 属性)。相反,你可能需要使用模板引擎(例如 Django 模板语言,旨在轻松地创建模板化文本文件)来自定义 .py 文件,或者使用 Rick Copeland 的 MetaPython 扩展。
如果你尝试在模块编译时进行更改,请注意你不必回退到文本;你可以直接编译 AST,而无需将其重新变成 .py 文件。
但在几乎任何情况下,你可能都试图做一些动态的事情,而像 Python 这样的语言实际上使这些事情非常容易,而不需要编写新的 .py 文件!如果你扩展你的问题,让我们知道你真正想要实现什么,新的 .py 文件可能根本不会涉及到答案;我见过数百个 Python 项目实际完成了数百个现实世界的任务,其中没有一个需要编写 .py 文件。所以,我必须承认,我有点怀疑你是否找到了第一个好的用例。 :-)
更新: 现在你已经解释了你想要做什么,我会倾向于直接在AST上操作。你需要通过删除整个语句来进行变异,而不是文件的行(这可能会导致一些只用SyntaxError死亡的半语句),那么在AST中进行操作何其更好?
ast
模块可以解析和修改代码结构,我将在稍后的示例中展示。但是,仅使用 ast
模块无法写回修改后的源代码。有其他模块可用于此工作,例如这里。ast
模块的入门教程,但更全面的使用指南可在Green Tree snakes教程和官方文档中了解。
ast
简介:
>>> import ast
>>> tree = ast.parse("print 'Hello Python!!'")
>>> exec(compile(tree, filename="<ast>", mode="exec"))
Hello Python!!
ast.parse()
来解析 Python 代码(以字符串表示)。这将返回抽象语法树(AST)结构的句柄。有趣的是,您可以编译回这个结构并像上面展示的那样执行它。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=[]))])"
ast
修改代码:
现在,让我们来看一个使用ast
模块修改Python代码的示例。修改AST结构的主要工具是ast.NodeTransformer
类。每当需要修改AST时,他/她需要从中进行子类化,并相应地编写节点转换。#!/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()
print " x is %s" % ("Hello Python")
。我最近创建了一段非常稳定(核心经过了很好的测试)且可扩展的代码,它可以从ast
树生成代码:https://github.com/paluh/code-formatter。
我正在将我的项目作为小型vim插件的基础(我每天都在使用),因此我的目标是生成非常漂亮和易读的Python代码。
P.S. 我曾尝试扩展codegen
,但它的架构基于ast.NodeVisitor
接口,因此格式化程序(visitor_
方法)只是函数。我发现这种结构非常受限制,难以优化(在长而嵌套的表达式的情况下,保留对象树并缓存一些部分结果更容易 - 否则您可能会遇到指数复杂度,如果您想搜索最佳布局)。但是,像mitsuhiko的每个作品一样,codegen
非常精简且写得很好。
codegen
,但似乎已被astor
取代。当前astor
版本(截至本文撰写时为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上进行了测试。
最近我写了一个小工具箱来进行基于纯AST的重构,叫做refactor。例如,如果你想用42
替换所有的placeholder
,你可以简单地编写如下规则:
class Replace(Rule):
def match(self, node):
assert isinstance(node, ast.Name)
assert node.id == 'placeholder'
replacement = ast.Constant(42)
return ReplacementAction(node, replacement)
它将找到所有可接受的节点,用新节点替换它们并生成最终表单;
--- test_file.py
+++ test_file.py
@@ -1,11 +1,11 @@
def main():
- print(placeholder * 3 + 2)
- print(2 + placeholder + 3)
+ print(42 * 3 + 2)
+ print(2 + 42 + 3)
# some commments
- placeholder # maybe other comments
+ 42 # maybe other comments
if something:
other_thing
- print(placeholder)
+ print(42)
if __name__ == "__main__":
main()