简体   繁体   English

将节点插入抽象语法树

[英]Insert a node into an abstract syntax tree

The ast module's documentation explains how to replace a node in the AST using the NodeTransformer class, but does not explain how to insert a new node into the tree. ast 模块的文档解释了如何使用NodeTransformer类替换 AST 中的节点,但没有解释如何将新节点插入到树中。

For example, given this module:例如,给定这个模块:

import foo
import bar

class Baz(object):

    def spam(self):
        pass

I would like to add another import, and set a class variable on Baz .我想添加另一个导入,并在Baz上设置一个类变量。

How can I create and insert these nodes into the AST?如何创建这些节点并将其插入到 AST 中?

Python ASTs are essentially composed of nested lists, so new nodes can be inserted into these lists once they have been constructed. Python AST 本质上由嵌套列表组成,因此一旦构建了新节点,就可以将它们插入到这些列表中。

First, get the AST that is to be changed:首先,获取要更改的AST:

>>> root = ast.parse(open('test.py').read())

>>> ast.dump(root)
"Module(body=[Import(names=[alias(name='foo', asname=None)]), Import(names=[alias(name='bar', asname=None)]), ClassDef(name='Baz', bases=[Name(id='object', ctx=Load())], body=[FunctionDef(name='spam', args=arguments(args=[Name(id='self', ctx=Param())], vararg=None, kwarg=None, defaults=[]), body=[Pass()], decorator_list=[])], decorator_list=[])])"

We can see that the outer Module has a body attribute that contains the top level elements of the module:我们可以看到,外层 Module 有一个body属性,其中包含模块的顶级元素:

>>> root.body
[<_ast.Import object at 0x7f81685385d0>, <_ast.Import object at 0x7f8168538950>, <_ast.ClassDef object at 0x7f8168538b10>]

Construct an import node and insert:构造一个导入节点并插入:

>>> import_node = ast.Import(names=[ast.alias(name='quux', asname=None)])
>>> root.body.insert(2, import_node)

Like the root module node, the class definition node has a body attribute that contains its members:与根模块节点一样,类定义节点有一个包含其成员的body属性:

>>> classdef = root.body[-1]
>>> ast.dump(classdef)
"ClassDef(name='Baz', bases=[Name(id='object', ctx=Load())], body=[FunctionDef(name='spam', args=arguments(args=[Name(id='self', ctx=Param())], vararg=None, kwarg=None, defaults=[]), body=[Pass()], decorator_list=[])], decorator_list=[])"

So we construct an assignment node and insert it:所以我们构造一个赋值节点并插入它:

>>> assign_node = ast.Assign(targets=[ast.Name(id='eggs', ctx=ast.Store())], value=ast.Str(s='ham')) 
>>> classdef.body.insert(0, assign_node)

To finish, fix up line numbers:最后,修复行号:

>>> ast.fix_missing_locations(root)
<_ast.Module object at 0x7f816812ef90>

We can verify that our nodes are in place by dumping the root node with ast.dump , using the unparse * tool from the CPython repository to generate source from the AST or using ast.unparse for Python 3.9 and later.我们可以通过使用ast.dump转储根节点、使用 CPython 存储库中的 unparse *工具从 AST 生成源代码或使用ast.unparse为 Python 3.9 及更高版本来验证我们的节点是否就位。

The Python3 unparse script ** can be found in the Tools directory of the CPython repository. Python3 解析脚本**可以在 CPython 存储库的Tools目录中找到。 In Python2 it was located in the Demo directory.在 Python2 中,它位于Demo目录中。

>>> from unparse import Unparser
>>> buf = StringIO()
>>> Unparser(root, buf)
<unparse.Unparser instance at 0x7f81685c6248>
>>> buf.seek(0)
>>> print(buf.read())

import foo
import bar
import quux

class Baz(object):
    eggs = 'ham'

    def spam(self):
        pass
>>> 

Using ast.unparse :使用ast.unparse

>>> unparsed = ast.unparse(root)
>>> print(unparsed)

When constructing AST nodes, you can get an idea of what the node should look like by using ast.parse and ast.dump (observe that ast.parse wraps the statement in a module):在构建 AST 节点时,您可以通过使用ast.parseast.dumpast.parse节点应该是什么样子(注意ast.parse将语句包装在一个模块中):

>>> root = ast.parse('import foo')
>>> ast.dump(root)
"Module(body=[Import(names=[alias(name='foo', asname=None)])])"

* Credit to this answer for documenting the existence of the unparse script. *归功于此答案记录了未解析脚本的存在。

** Use the version of the script from the git branch that corresponds to the Python version being used. **使用 git 分支中与正在使用的 Python 版本相对应的脚本版本。 For example, using the script from the 3.6 branch on 3.7 code may fail due to differences in the versions' respective grammars.例如,在 3.7 代码上使用来自 3.6 分支的脚本可能会由于版本各自的语法差异而失败。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM