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

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

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


当前回答

程序转换系统是一种工具,它可以解析源文本,构建ast,允许您使用源到源转换(“如果您看到这个模式,请将其替换为那个模式”)来修改它们。这样的工具非常适合对现有源代码进行突变,即“如果看到此模式,请用模式变体替换”。

当然,您需要一个程序转换引擎,它可以解析您感兴趣的语言,并仍然执行面向模式的转换。我们的DMS软件再造工具包就是一个可以做到这一点的系统,它可以处理Python和各种其他语言。

请参阅这个SO答案,以获得一个用于Python准确捕获注释的dms解析AST示例。DMS可以对AST进行更改,并重新生成有效文本,包括注释。您可以要求它使用自己的格式约定(您可以更改这些约定)对AST进行美化打印,或者进行“保真打印”,即使用原始的行和列信息来最大限度地保留原始布局(插入新代码时不可避免地会对布局进行一些更改)。

要用DMS实现Python的“突变”规则,您可以编写以下代码:

rule mutate_addition(s:sum, p:product):sum->sum =
  " \s + \p " -> " \s - \p"
 if mutate_this_place(s);

该规则以语法正确的方式将“+”替换为“-”;它对AST进行操作,因此不会触及碰巧看起来正确的字符串或注释。“mutate_this_place”上的额外条件是让您控制这种情况发生的频率;你不希望改变程序中的每一个地方。

显然,您需要更多这样的规则来检测各种代码结构,并将它们替换为变异的版本。DMS乐于应用一组规则。突变的AST随后被漂亮地打印出来。

其他回答

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

保持周围源代码的语法完整性(例如保留注释,其他类型的代码格式) 实际上使用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()

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

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

另一种回答建议使用密码原,它似乎已被阿斯特取代。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()并执行结果代码对象即可。

在另一个答案中,我建议使用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上进行了测试。