2009-10-04 15 views
37

¿Alguien tiene un ejemplo simple usando ast.NodeVisitor para recorrer el árbol de sintaxis abstracta en Python 2.6? La diferencia entre visit y generic_visit no está clara para mí, y no puedo encontrar ningún ejemplo usando google codesearch o plain google.Ejemplo simple de cómo usar ast.NodeVisitor?

Respuesta

68

ast.visit - menos que lo sustituya en una subclase, por supuesto - cuando se llama a visitar una clase de ast.Nodefoo, pide self.visit_foo si existe ese método, de lo contrario self.generic_visit. Este último, de nuevo en su implementación en la clase ast, simplemente llama al self.visit en cada nodo secundario (y no realiza ninguna otra acción).

Por lo tanto, consideramos, por ejemplo:

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

Aquí, estamos anulando generic_visit para imprimir el nombre de la clase, pero también llamar a la clase base (por lo que también se visitaron todos los niños) Así, por ejemplo ...:

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

emite:

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

Pero supongamos que no cuidar de los nodos de carga (y los niños de los mismos - si tenían alguna ;-). A continuación, una forma sencilla de hacer frente a ese podría ser, por ejemplo:

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

Ahora, cuando estamos visitando un nodo de carga, visit despachos, NO a generic_visit más, pero a nuestro nuevo visit_Load ... lo que doesn' hacer cualquier cosa. Entonces:

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

o, supongamos que también quisiéramos ver los nombres reales de los nodos Nombre; entonces ...:

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

Pero, NodeVisitor es una clase porque esto le permite almacenar información durante una visita. Supongamos que todo lo que queremos es el conjunto de nombres en un "módulo". Entonces no necesitamos para anular generic_visit más, sino más bien ...:

>>> 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'] 

Este tipo de cosas es un caso de uso más típico que los que requieren reemplazos de generic_visit - normalmente, sólo está interesado en algunos tipos de nodos, como estamos aquí en Módulo y Nombre, por lo que podemos simplemente anular visit_Module y visit_Name y dejar que ast 0's visit haga el envío en nuestro nombre.

+0

Gracias! - Esto es exactamente lo que estaba buscando. – lacker

+1

@lacker, de nada! –

+0

Genial, me gustaría entender un poco más la diferencia entre compiler.ast y ast ahora ... –

5

generic_visit se llama cuando no se puede encontrar un visitante personalizado (es decir, nombre_de_la_compra). Aquí hay un fragmento de código que escribí recientemente con ast.NodeVisitor: https://github.com/pypy/pypy/blob/master/py/_code/_assertionnew.py. Interpreta los nodos AST para obtener información de depuración sobre algunos de ellos y vuelve a aparecer con generic_visit cuando no se proporciona una implementación especial.

12

Mirando el código en ast.py no es tan difícil copiar y pegar su propio andador. P.ej.

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

Imprime

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