55

有没有人有一个使用 ast.NodeVisitor 在 Python 2.6 中遍历抽象语法树的简单示例?我不清楚 visit 和 generic_visit 之间的区别,我找不到任何使用谷歌代码搜索或普通谷歌的例子。

4

3 回答 3

91

ast.visit-- 当然,除非你在子类中重写它 -- 当调用访问一个ast.Nodefoo时,如果该方法存在则调用self.visit_foo,否则调用self.generic_visit。后者,同样在类ast本身的实现中,只调用self.visit每个子节点(并且不执行其他操作)。

因此,例如考虑:

>>> class v(ast.NodeVisitor):
...   def generic_visit(self, node):
...     print type(node).__name__
...     ast.NodeVisitor.generic_visit(self, node)
... 

在这里,我们重写generic_visit了打印类名,但调用了基类(这样所有子类也将被访问)。所以例如...:

>>> x = v()
>>> t = ast.parse('d[x] += v[y, x]')
>>> x.visit(t)

发出:

Module
AugAssign
Subscript
Name
Load
Index
Name
Load
Store
Add
Subscript
Name
Load
Index
Tuple
Name
Load
Name
Load
Load
Load

但是假设我们不关心 Load 节点(及其子节点——如果它们有的话;-)。那么一个简单的处理方法可能是,例如:

>>> class w(v):
...   def visit_Load(self, node): pass
... 

现在,当我们访问 Load 节点时,visit分派,generic_visit不再发送到,而是发送到我们的 new visit_Load...,它根本不做任何事情。所以:

>>> y = w()
>>> y.visit(t)
Module
AugAssign
Subscript
Name
Index
Name
Store
Add
Subscript
Name
Index
Tuple
Name
Name

或者,假设我们还想查看 Name 节点的实际名称;然后...:

>>> class z(v):
...   def visit_Name(self, node): print 'Name:', node.id
... 
>>> z().visit(t)
Module
AugAssign
Subscript
Name: d
Index
Name: x
Store
Add
Subscript
Name: v
Index
Tuple
Name: y
Name: x
Load
Load

但是,NodeVisitor 是一个类,因为它可以在访问期间存储信息。假设我们想要的只是“模块”中的一组名称。那么我们就不需要再重写generic_visit了,而是……:

>>> class allnames(ast.NodeVisitor):
...   def visit_Module(self, node):
...     self.names = set()
...     self.generic_visit(node)
...     print sorted(self.names)
...   def visit_Name(self, node):
...     self.names.add(node.id)
... 
>>> allnames().visit(t)
['d', 'v', 'x', 'y']

这种事情是一个比需要覆盖的更典型的用例generic_visit——通常,你只对几种节点感兴趣,就像我们在模块和名称中一样,所以我们可以覆盖visit_Modulevisit_Name让 astvisit做代表我们的调度。

于 2009-10-04T02:05:23.483 回答
14

查看ast.py中的代码,复制粘贴并滚动您自己的步行器并不难。例如

import ast
def str_node(node):
    if isinstance(node, ast.AST):
        fields = [(name, str_node(val)) for name, val in ast.iter_fields(node) if name not in ('left', 'right')]
        rv = '%s(%s' % (node.__class__.__name__, ', '.join('%s=%s' % field for field in fields))
        return rv + ')'
    else:
        return repr(node)
def ast_visit(node, level=0):
    print('  ' * level + str_node(node))
    for field, value in ast.iter_fields(node):
        if isinstance(value, list):
            for item in value:
                if isinstance(item, ast.AST):
                    ast_visit(item, level=level+1)
        elif isinstance(value, ast.AST):
            ast_visit(value, level=level+1)


ast_visit(ast.parse('a + b'))

打印出来

Module(body=[<_ast.Expr object at 0x02808510>])
  Expr(value=BinOp(op=Add()))
    BinOp(op=Add())
      Name(id='a', ctx=Load())
        Load()
      Add()
      Name(id='b', ctx=Load())
        Load()
于 2014-10-15T05:17:50.250 回答
6

generic_visit当无法找到自定义访问者(即 visit_Name)时调用。这是我最近用 ast.NodeVisitor 编写的一段代码:https ://foss.heptapod.net/pypy/pypy/-/blob/80ead76ab428100ffeb01109c7fc0d94f1048af2/py/_code/_assertionnew.py它解释 AST 节点以获取有关调试信息其中一些并在generic_visit未提供特殊实现时退回。

于 2009-10-04T01:52:15.023 回答