将Python对象转换为Python AST节点

5

我需要将修改后的Python对象转换回源代码。因此,我尝试寻找一些方法将真正的Python对象转换为Python ast.Node(稍后在astor库中使用以转储源代码)。

以下是我想要的用法示例,适用于Python 2:

import ast
import importlib

import astor


m = importlib.import_module('something')

# modify an object
m.VAR.append(123)

ast_nodes = some_magic(m)

source = astor.dump(ast_nodes)

请帮我找到那个神奇的东西


1
为什么?这似乎是你在描述你想要的解决方案,而不是问题本身。如果我不得不猜测,你正在尝试将对象的状态保留到下一次代码运行。这是一个完美的情况,可以将你的对象序列化为JSON格式并保存到文件中!然后当代码再次运行时,你只需从文件反序列化即可。 - Aaron3468
也许吧。问题是,我想通过生成模板模型、表单、模板、视图和URL来加快开发简单的Django页面。而问题在于修改Django URL。它是一个列表。所以我不能只在文件末尾写入。 - Vladimir
使用像pickle、JSON等序列化格式。 - juanpa.arrivillaga
@Aaron3468,谢谢你的帮助,但我们需要将其转换回源代码。这是为了人类。例如-生成新的Django模型和URL。 - Vladimir
2个回答

8
无法按照您想要的方式操作,因为这不是AST的工作方式。 当解释器运行代码时,它将从源文件生成AST,并解释该AST以生成Python对象。 一旦生成了这些对象,它们发生了什么与AST无关。
但是,可以获取首次生成对象的AST。 模块inspect允许您获取某些Python对象的源代码。
import ast
import importlib
import inspect

m = importlib.import_module('pprint')
s = inspect.getsource(m)
a = ast.parse(s)
print(ast.dump(a))
# Prints the AST of the pprint module

但是getsource()的命名很恰当。 如果我改变了m中某个变量(或任何其他对象)的值,它不会改变其源代码。
即使可以从对象重新生成AST,some_magic()也不会有一个单一的解决方案。 想象一下,在某个模块中有一个变量x,我在另一个模块中重新分配它:
# In some_module.py
x = 0

# In __main__.py
m = importlib.import_module('some_module')
m.x = 1 + 227

现在,m.x 的值是 228,但没有办法知道导致该值的表达式是什么(好吧,除非读取__main__.py的AST,但这很快会失控)。它是仅仅一个字面量吗?还是函数调用的结果?
如果您真的必须在修改模块的某个值后获取新的AST,则最好的解决方案是自己转换原始AST。您可以找到标识符获取其值的位置,并将赋值的值替换为您想要的任何内容。例如,在我的小例子中,x = 0由以下AST表示:
Assign(targets=[Name(id='x', ctx=Store())], value=Num(n=0))

要获取与我在 __main__.py 中重新分配匹配的AST,我需要将上述 Assign 节点的值更改为以下内容:
value=BinOp(left=Num(n=1), op=Add(), right=Num(n=227))

如果您想走这条路,我建议您查看Python AST节点转换器(ast.NodeTransformer)的文档,以及这本优秀的手册,它记录了您可以在Python AST中遇到的所有节点。请点击ast.NodeTransformerGreen Tree Snakes - the missing Python AST docs

0

Vladimir所询问的对于编译器优化确实是有用的。事实上,使用ast库可以实现这一点。这里是一个简单的示例,演示常量函数的求值:

from ast import *
import numpy as np

PURE_FUNS = {'arange' : np.arange}
PROG = '''
A=arange(5)
B=[0, 1, 2, 3, 4]
A[2:3] = 1
C = [A[1], 2, m]
'''

def py_to_ast(o):
    if type(o) == np.ndarray:
        return List(elts=[py_to_ast(e) for e in o], ctx=Load())
    elif type(o) == np.int64:
        return Constant(value=o)
    # Add elifs for more types here
    else:
        assert False

class EvalPureFuns(NodeTransformer):
    def visit_Call(self, node):
        is_const_args = all(type(a) == Constant for a in node.args)
        if node.func.id in PURE_FUNS and is_const_args:
            res = eval(unparse(node), PURE_FUNS)
            return py_to_ast(res)
        return node

node = parse(PROG)    
node = EvalPureFuns().visit(node)    
print(unparse(node))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接