将 python 个对象转换为 python 个 AST 节点

Convert python objects to python AST-nodes

我需要将修改后的 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)

请帮我找到 some_magic

没有办法做你想做的事,因为那不是 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() 可以 return。 假设我在某个模块中有一个变量 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))

为了让 AST 与我在 __main__.py 中所做的重新分配相匹配,我必须将上述 Assign 节点的值更改为以下内容:

value=BinOp(left=Num(n=1), op=Add(), right=Num(n=227))

如果您想那样做,我建议您查看 Python 的 AST 节点转换器文档 (ast.NodeTransformer), as well as this excellent manual that documents all the nodes you can meet in Python ASTs Green Tree Snakes - the missing Python AST docs.

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))