将节点插入抽象语法树 [英] Insert a node into an abstract syntax tree

查看:93
本文介绍了将节点插入抽象语法树的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

ast模块的文档介绍了如何替换AST中的节点使用 NodeTransformer 类,但没有说明如何插入



例如,给定此模块:

  import foo 
import bar

class Baz(object):

def spam(self):
pass

我想添加另一个导入,并在 Baz 。



如何创建这些节点并将其插入AST?

解决方案

Python AST本质上是由嵌套列表组成的,因此一旦构建了新节点,便可以将其插入到这些列表中。 / p>

首先,获取要更改的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 = [] )])

我们可以看到外部模块具有 body 属性,该属性包含模块的顶级元素:

 >> root.body 
[< _ast.Import对象位于0x7f81685385d0>,< _ast.Import对象位于0x7f8168538950>,< _ast.ClassDef对象位于0x7f8168538b10>]

构造一个导入节点并插入:

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

类似于根模块节点,类定义节点具有包含其成员的 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 = [])

因此,我们构造了一个分配节点并将其插入:

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

最后,确定行号:

 >> ast.fix_missing_locations(root)
< _ast.Module对象位于0x7f816812ef90>

我们可以通过使用转储根节点来验证我们的节点是否到位ast.dump ,使用CPython存储库中的unparse * 工具从AST生成源,或使用 ast.unparse (适用于Python 3.9及更高版本)。



Python3未解析脚本 ** 可以在工具目录。在Python2中,它位于 Demo 目录中。

 >>从unparse导入Unparser 
>> buf = StringIO()
>> Unparser(root,buf)
< unparse.Unparser实例位于0x7f81685c6248>
>> buf.seek(0)
>> print(buf.read())

导入foo
导入栏
导入quux

类Baz(object):
蛋= 'ham'

def垃圾邮件(自己):
pass
>>

使用 ast.unparse

 >>未解析= ast.unparse(root)
>> print(unparsed)

构造AST节点时,您可以了解该节点的外观通过使用 ast.parse ast.dump (观察 ast.parse 将语句包装在模块中):

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

* 归功于此答案记录未分析脚本的存在。



** 使用git分支中与该脚本相对应的脚本版本使用的是Python版本。例如,由于版本各自的语法差异,在3.6代码上使用3.6分支中的脚本可能会失败。


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.

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.

How can I create and insert these nodes into the AST?

解决方案

Python ASTs are essentially composed of nested lists, so new nodes can be inserted into these lists once they have been constructed.

First, get the AST that is to be changed:

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

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

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

The Python3 unparse script** can be found in the Tools directory of the CPython repository. In Python2 it was located in the Demo directory.

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

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

>>> 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. For example, using the script from the 3.6 branch on 3.7 code may fail due to differences in the versions' respective grammars.

这篇关于将节点插入抽象语法树的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆