2009-10-04 16 views
37

Czy ktoś ma prosty przykład użycia ast.NodeVisitor do przejścia abstrakcyjnego drzewa składni w Pythonie 2.6? Różnica między wizytą a generic_visit jest dla mnie niejasna i nie mogę znaleźć żadnego przykładu za pomocą google codesearch lub zwykłego google.Prosty przykład użycia ast.NodeVisitor?

Odpowiedz

68

ast.visit - chyba go nadpisać w podklasie, oczywiście - kiedy nazywa się odwiedzić ast.Node klasy foo wzywa self.visit_foo jeśli ta metoda nie istnieje, w przeciwnym razie self.generic_visit. Ten drugi, ponownie w swojej implementacji w klasie ast, po prostu wywołuje na każdym węźle podrzędnym (i nie wykonuje żadnej innej akcji).

więc rozważyć, na przykład:

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

Tutaj jesteśmy nadrzędnymi generic_visit wydrukować nazwę klasy, ale również wywołanie do klasy bazowej (tak, że wszystkie dzieci będą również odwiedził). Tak na przykład ...:

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

emituje:

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

Ale załóżmy, że nie dba o węzłach ładunkowych (i jego dzieci - gdyby każdy ;-). Następnie w prosty sposób poradzić sobie z tym może być, na przykład:

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

Teraz, kiedy odwiedzasz węzeł obciążenia, visit depesze, nie generic_visit więcej, ale do naszego nowego visit_Load ... co robi” w ogóle zrobić cokolwiek. A więc:

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

lub, przypuśćmy, że chcieliśmy zobaczyć rzeczywiste nazwy węzłów Name; następnie ...:

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

Ale NodeVisitor jest klasą, ponieważ pozwala jej przechowywać informacje podczas wizyty. Załóżmy, że chcemy tylko zestawu nazw w "module". Wtedy nie musimy przesłonić generic_visit więcej, ale raczej ...:

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

Tego rodzaju rzeczy jest więcej niż typowy przypadek użycia tych wymagających przesłonięcia z generic_visit - normalnie, jesteś tylko zainteresowany w kilku rodzajach węzłów, tak jak jesteśmy tutaj w module i nazwie, więc możemy po prostu przesłonić visit_Module i visit_Name i pozwolić ast visit zrobić wysyłkę w naszym imieniu.

+0

Dziękujemy! - właśnie tego szukałem. – lacker

+1

@lacker, nie ma za co! –

+0

Świetne, chciałbym zrozumieć nieco więcej różnic między kompilatorem a ast teraz ... –

5

generic_visit jest wywoływana, gdy nie można znaleźć niestandardowego użytkownika (np. Nazwa_domeny). Oto fragment kodu, który napisałem ostatnio z ast.NodeVisitor: https://github.com/pypy/pypy/blob/master/py/_code/_assertionnew.py Interpretuje węzły AST w celu uzyskania informacji o debugowaniu niektórych z nich i ponownie wraca z generic_visit, gdy nie jest dostarczana specjalna implementacja.

12

Patrząc na kod w ast.py nie jest trudno skopiować i wkleić własnego chodzik. Na przykład.

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

Drukuje

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