我想以编程方式编辑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随后被漂亮地打印出来。
其他回答
在另一个答案中,我建议使用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上进行了测试。
程序转换系统是一种工具,它可以解析源文本,构建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随后被漂亮地打印出来。
如果你在2019年看到这个,那么你可以使用这个libcst 包中。它的语法类似ast。这就像一个魅力,并保留了代码结构。这对于你必须保留注释、空格、换行符等的项目是很有帮助的。
如果您不需要关心保留注释、空格和其他内容,那么ast和astor的组合可以很好地工作。
花了一些时间,但是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对象。
我们也有类似的需求,这里的其他答案并没有解决这个问题。因此,我们为此创建了一个库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
希望这能有所帮助!