我想以编程方式编辑python源代码。基本上我想读取一个.py文件,生成AST,然后写回修改后的python源代码(即另一个.py文件)。
有一些方法可以使用标准的python模块(如ast或compiler)来解析/编译python源代码。但是,我认为它们都不支持修改源代码的方法(例如删除这个函数声明),然后再写回修改的python源代码。
更新:我想这样做的原因是我想为python写一个突变测试库,主要是通过删除语句/表达式,重新运行测试,看看有什么破坏。
我想以编程方式编辑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,允许您使用源到源转换(“如果您看到这个模式,请将其替换为那个模式”)来修改它们。这样的工具非常适合对现有源代码进行突变,即“如果看到此模式,请用模式变体替换”。
当然,您需要一个程序转换引擎,它可以解析您感兴趣的语言,并仍然执行面向模式的转换。我们的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随后被漂亮地打印出来。
我以前使用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转换时更有用。
红男爵计划也可能是个不错的选择(泽维尔·康贝尔)
在另一个答案中,我建议使用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上进行了测试。
我们也有类似的需求,这里的其他答案并没有解决这个问题。因此,我们为此创建了一个库ASTTokens,它采用AST或astroid模块生成的AST树,并用原始源代码中的文本范围标记它。
它不直接修改代码,但在上面添加代码并不难,因为它会告诉您需要修改的文本范围。
例如,这将在WRAP(…)中包装一个函数调用,保留注释和其他内容:
example = """
def foo(): # Test
'''My func'''
log("hello world") # Print
"""
import ast, asttokens
atok = asttokens.ASTTokens(example, parse=True)
call = next(n for n in ast.walk(atok.tree) if isinstance(n, ast.Call))
start, end = atok.get_text_range(call)
print(atok.text[:start] + ('WRAP(%s)' % atok.text[start:end]) + atok.text[end:])
生产:
def foo(): # Test
'''My func'''
WRAP(log("hello world")) # Print
希望这能有所帮助!