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

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

更新:我想这样做的原因是我想为python写一个突变测试库,主要是通过删除语句/表达式,重新运行测试,看看有什么破坏。


当前回答

不幸的是,上面的答案实际上没有一个同时满足这两个条件

保持周围源代码的语法完整性(例如保留注释,其他类型的代码格式) 实际上使用AST(而不是CST)。

我最近写了一个小工具包来进行纯基于AST的重构,称为重构。例如,如果你想用42替换所有占位符,你可以简单地像这样写一个规则;

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()

其他回答

在另一个答案中,我建议使用astor包,但我后来发现了一个名为astunparse的最新AST非解析包:

>>> import ast
>>> import astunparse
>>> print(astunparse.unparse(ast.parse('def foo(x): return 2 * x')))


def foo(x):
    return (2 * x)

我已经在Python 3.5上进行了测试。

我以前使用baron,但现在已经切换到parso,因为它是现代python的最新版本。效果很好。

我还需要这个做变异测试。用parso做一个真的很简单,请访问https://github.com/boxed/mutmut查看我的代码

花了一些时间,但是Python 3.9有这个: https://docs.python.org/3.9/whatsnew/3.9.html#ast https://docs.python.org/3.9/library/ast.html#ast.unparse

ast.unparse(ast_obj)

取消解析ast.AST对象并生成一个字符串,如果使用ast.parse()进行解析,该字符串将生成一个等效的ast.AST对象。

内置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()并执行结果代码对象即可。

不幸的是,上面的答案实际上没有一个同时满足这两个条件

保持周围源代码的语法完整性(例如保留注释,其他类型的代码格式) 实际上使用AST(而不是CST)。

我最近写了一个小工具包来进行纯基于AST的重构,称为重构。例如,如果你想用42替换所有占位符,你可以简单地像这样写一个规则;

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()