如何使用ast.NodeVisitor的简单示例?
有没有人能给个简单的例子,说明怎么用 ast.NodeVisitor 在 Python 2.6 中遍历抽象语法树?我对 visit 和 generic_visit 之间的区别不太明白,而且在谷歌搜索或者代码搜索中找不到相关的例子。
3 个回答
generic_visit
是在找不到自定义访问器(比如 visit_Name)时被调用的。这里有一段我最近写的代码,使用了 ast.NodeVisitor:https://foss.heptapod.net/pypy/pypy/-/blob/80ead76ab428100ffeb01109c7fc0d94f1048af2/py/_code/_assertionnew.py。这段代码用来解释抽象语法树(AST)中的节点,以获取一些调试信息,当没有提供特定的实现时,就会使用 generic_visit
作为备用方案。
看看这个代码在 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()
ast.visit
这个方法的作用是,当你调用它去访问一个类型为 foo
的 ast.Node
时,如果你没有在子类中重写它,它会自动调用 self.visit_foo
方法(如果这个方法存在的话),如果不存在,就会调用 self.generic_visit
。而 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
,而是调用我们新的 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
的情况更常见——通常情况下,你只对几种节点感兴趣,就像我们这里的 Module 和 Name,所以我们只需要重写 visit_Module
和 visit_Name
,然后让 ast 的 visit
方法为我们处理调度。