解析.py文件,读取AST,修改它,然后写回修改后的源代码

Ror*_*ory 156 python compiler-construction abstract-syntax-tree

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

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

更新:我想这样做的原因是我想为python 编写一个Mutation测试库,主要是通过删除语句/表达式,重新运行测试和查看什么中断.

Rya*_*yan 69

Pythoscope对它自动生成的测试用例执行此操作,就像python 2.6 的2to3工具一样(它将python 2.x源转换为python 3.x源).

这两个工具都使用lib2to3库,它是python解析器/编译器机器的一个实现,当它从源 - > AST - >源进行往返时可以保留源代码中的注释.

如果您想进行更多重构,那么此项目可能会满足您的需求.

AST模块是你的其他选择,并有一个旧例子如何"unparse"语法树回代码(使用解析器模块).但是ast当对代码进行AST转换然后转换为代码对象时,该模块更有用.

redbaron项目也可能是一个不错的选择(HT泽维尔Combelle)

  • 仍然维护了unsarse示例,这里是更新的py3k版本:http://hg.python.org/cpython/log/tip/Tools/parser/unparse.py (5认同)
  • 我认为redbaron https://redbaron.readthedocs.org/en/latest/将是一个很好的补充 (4认同)
  • 关于 `unparse.py` 脚本 - 从另一个脚本使用它可能真的很麻烦。但是,有一个名为 astunparse([在 github](https://github.com/simonpercivall/astunparse),[在 pypi](https://pypi.python.org/pypi/astunparse))的包,它基本上是`unparse.py` 的正确打包版本。 (2认同)

Bri*_*ian 56

内置的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))
Run Code Online (Sandbox Code Playgroud)

这将打印:

def foo():
    return 42
Run Code Online (Sandbox Code Playgroud)

请注意,您可能会丢失确切的格式和注释,因为这些不会被保留.

但是,您可能不需要.如果您只需要执行替换的AST,则只需在ast上调用compile()并执行生成的代码对象即可.

  • 对于将来使用它的任何人来说,codegen很大程度上已经过时并且有一些bug.我修了几个; 我将此作为github上的要点:https://gist.github.com/791312 (19认同)
  • [astor](https://pypi.org/project/astor/)似乎是codegen的维护继任者 (2认同)

Bra*_*des 19

您可能不需要重新生成源代码.当然,这对我来说有点危险,因为你实际上没有解释为什么你认为你需要生成一个充满代码的.py文件; 但:

  • 如果你想生成一个人们会实际使用的.py文件,也许这样他们可以填写一个表单并获得一个有用的.py文件插入到他们的项目中,那么你不想把它改成AST和因为你将失去所有的格式(想想通过将相关的行集合在一起使得Python具有可读性的空行)(ast节点有linenocol_offset属性)注释.相反,您可能希望使用模板引擎(例如,Django模板语言,旨在使模板甚至文本文件变得容易)来自定义.py文件,或者使用Rick Copeland的MetaPython扩展.

  • 如果您在编译模块期间尝试进行更改,请注意您不必一直回到文本; 你可以直接编译AST而不是把它变回.py文件.

  • 但几乎在任何情况下,你可能都试图做一些动态的东西,像Python这样的语言实际上很容易,而不用编写新的.py文件!如果你扩展你的问题让我们知道你真正想要完成什么,那么新的.py文件可能根本不会涉及答案; 我已经看到数百个Python项目正在处理数百个真实世界的东西,而且只需编写一个.py文件就不需要其中一个.所以,我必须承认,我有点怀疑你发现了第一个好的用例.:-)

更新:既然你已经解释了你想要做的事情,那么我很想在AST上进行操作.你会希望通过删除而不是文件的行来改变(这可能导致半语句只是死于SyntaxError),而是整个语句 - 还有什么比在AST中更好的地方呢?


arg*_*per 17

在一个不同的答案我建议使用该astor包,但我已经发现了一个更新的AST解析包,称为astunparse:

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


def foo(x):
    return (2 * x)
Run Code Online (Sandbox Code Playgroud)

我在Python 3.5上测试了这个.


Sam*_*rks 15

花了一段时间,但 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)
Run Code Online (Sandbox Code Playgroud)

解析一个 ast.AST 对象并生成一个带有代码的字符串,如果用 ast.parse() 解析回来,该字符串将产生一个等效的 ast.AST 对象。

  • 美丽的!这是当代 Python 版本的最佳解决方案。 (2认同)

ViF*_*iFI 9

ast模块的帮助下,解析和修改代码结构当然是可能的,我稍后会在一个例子中展示它.但是,ast单独使用模块无法写回修改后的源代码.此工作还有其他模块,例如此处的模块.

注意:下面的示例可以作为ast模块使用的入门教程处理,但有关使用ast模块的更全面的指南,请参阅Green Tree snakes教程模块的官方文档ast.

简介ast:

>>> import ast
>>> tree = ast.parse("print 'Hello Python!!'")
>>> exec(compile(tree, filename="<ast>", mode="exec"))
Hello Python!!
Run Code Online (Sandbox Code Playgroud)

您只需调用API即可解析python代码(以字符串表示)ast.parse().这将返回抽象语法树(AST)结构的句柄.有趣的是,您可以编译回这个结构并执行它,如上所示.

另一个非常有用的API是以ast.dump()字符串形式转储整个AST.它可用于检查树结构,在调试时非常有用.例如,

在Python 2.7上:

>>> import ast
>>> tree = ast.parse("print 'Hello Python!!'")
>>> ast.dump(tree)
"Module(body=[Print(dest=None, values=[Str(s='Hello Python!!')], nl=True)])"
Run Code Online (Sandbox Code Playgroud)

在Python 3.5上:

>>> import ast
>>> tree = ast.parse("print ('Hello Python!!')")
>>> ast.dump(tree)
"Module(body=[Expr(value=Call(func=Name(id='print', ctx=Load()), args=[Str(s='Hello Python!!')], keywords=[]))])"
Run Code Online (Sandbox Code Playgroud)

请注意Python 2.7与Python 3.5中print语句的语法差异以及各个树中AST节点类型的差异.


如何修改代码使用ast:

现在,让我们来看一个按ast模块修改python代码的例子.修改AST结构的主要工具是ast.NodeTransformerclass.每当需要修改AST时,他/她需要从中进行子类化并相应地编写节点转换.

对于我们的示例,让我们尝试编写一个简单的实用程序,它将Python 2,print语句转换为Python 3函数调用.

打印语句到Fun call converter实用程序:print2to3.py:

#!/usr/bin/env python
'''
This utility converts the python (2.7) statements to Python 3 alike function calls before running the code.

USAGE:
     python print2to3.py <filename>
'''
import ast
import sys

class P2to3(ast.NodeTransformer):
    def visit_Print(self, node):
        new_node = ast.Expr(value=ast.Call(func=ast.Name(id='print', ctx=ast.Load()),
            args=node.values,
            keywords=[], starargs=None, kwargs=None))
        ast.copy_location(new_node, node)
        return new_node

def main(filename=None):
    if not filename:
        return

    with open(filename, 'r') as fp:
        data = fp.readlines()
    data = ''.join(data)
    tree = ast.parse(data)

    print "Converting python 2 print statements to Python 3 function calls"
    print "-" * 35
    P2to3().visit(tree)
    ast.fix_missing_locations(tree)
    # print ast.dump(tree)

    exec(compile(tree, filename="p23", mode="exec"))

if __name__ == '__main__':
    if len(sys.argv) <=1:
        print ("\nUSAGE:\n\t print2to3.py <filename>")
        sys.exit(1)
    else:
        main(sys.argv[1])
Run Code Online (Sandbox Code Playgroud)

可以在小示例文件上尝试此实用程序,例如下面的一个,它应该可以正常工作.

测试输入文件:py2.py

class A(object):
    def __init__(self):
        pass

def good():
    print "I am good"

main = good

if __name__ == '__main__':
    print "I am in main"
    main()
Run Code Online (Sandbox Code Playgroud)

请注意,上述转换仅用于ast教程目的,在实际情况下,必须查看所有不同的场景,例如print " x is %s" % ("Hello Python").


pal*_*luh 6

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

我正在使用我的项目作为小vim插件的基础(我每天都在使用它),因此我的目标是生成非常好的和可读的python代码.

PS我试图扩展,codegen但它的架构基于ast.NodeVisitor接口,因此格式化程序(visitor_方法)只是函数.我发现这种结构非常有限并且难以优化(在长和嵌套表达式的情况下,更容易保留对象树并缓存一些部分结果 - 换句话说,如果你想搜索最佳布局,你可以达到指数复杂性). codegen由于每一块光彦的工作(这是我读过)是写得很好,简洁.


小智 6

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

如果你不需要关心保留的注释、空格等,那么 ast 和astor的组合效果很好。


arg*_*per 5

其他答案之一建议codegen,该答案似乎已被取代astorastorPyPI的版本(撰写本文时为 0.5 版)似乎也有点过时,因此您可以安装开发版本,astor如下所示。

pip install git+https://github.com/berkerpeksag/astor.git#egg=astor
Run Code Online (Sandbox Code Playgroud)

然后您可以使用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
Run Code Online (Sandbox Code Playgroud)

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