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

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

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


当前回答

我最近创建了相当稳定的(核心是经过良好测试的)和可扩展的代码段,它从ast树生成代码:https://github.com/paluh/code-formatter。

我正在使用我的项目作为一个小vim插件的基础(我每天都在使用),所以我的目标是生成非常漂亮和可读的python代码。

P.S. I've tried to extend codegen but it's architecture is based on ast.NodeVisitor interface, so formatters (visitor_ methods) are just functions. I've found this structure quite limiting and hard to optimize (in case of long and nested expressions it's easier to keep objects tree and cache some partial results - in other way you can hit exponential complexity if you want to search for best layout). BUT codegen as every piece of mitsuhiko's work (which I've read) is very well written and concise.

其他回答

另一种回答建议使用密码原,它似乎已被阿斯特取代。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上进行了测试。

我们也有类似的需求,这里的其他答案并没有解决这个问题。因此,我们为此创建了一个库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

希望这能有所帮助!

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

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

如果你在2019年看到这个,那么你可以使用这个libcst 包中。它的语法类似ast。这就像一个魅力,并保留了代码结构。这对于你必须保留注释、空格、换行符等的项目是很有帮助的。

如果您不需要关心保留注释、空格和其他内容,那么ast和astor的组合可以很好地工作。