In der vorherigen Einführung in das Ast-Modul von Python (nach dem abstrakten Syntaxbaum) habe ich Ihnen vorgestellt, wie Sie dem abstrakten Syntaxbaum mithilfe der Hilfsfunktion des Ast-Moduls folgen können. tat.
Eine Möglichkeit besteht darin, die Hilfsfunktion des ast-Moduls zu verwenden. Wenn Sie jedoch * ast.NodeVisitor * verwenden, können Sie mehr tun. Sie können dem abstrakten Syntaxbaum leicht folgen. Es ist einfacher zu verstehen, dass das, was Sie tun, dasselbe ist wie die Verwendung einer Hilfsfunktion, wenn Sie sich die Implementierung von * NodeVisitor * ansehen. Ich werde sie daher anhand dieser Implementierung vorstellen. * NodeVisitor * ist eines der Entwurfsmuster mit dem Namen Besuchermuster.
3.4
class NodeVisitor(object):
def visit(self, node):
"""Visit a node."""
method = 'visit_' + node.__class__.__name__
visitor = getattr(self, method, self.generic_visit)
return visitor(node)
def generic_visit(self, node):
"""Called if no explicit visitor function exists for a node."""
for field, value in iter_fields(node):
if isinstance(value, list):
for item in value:
if isinstance(item, AST):
self.visit(item)
elif isinstance(value, AST):
self.visit(value)
Wenn * visit_NodeClassname * nicht definiert ist, verwenden Sie * ast.iter_fields *, um dem abstrakten Syntaxbaum * generic_visit * zu folgen. Es wird ausgeführt. Die Knotenklasse des abstrakten Syntaxbaums verwendet * ast.AST * als Basisklasse, also * isinstance (Wert, AST) ) * Bestimmt, ob es sich um eine Knoteninstanz handelt, und durchläuft sie rekursiv (* self.visit () *).
Lass es uns tatsächlich benutzen. Definiert eine Klasse, die von * NodeVisitor * erbt.
3.4
>>> import ast
>>> source = """
... import sys
... def hello(s):
... print('hello {}'.format(s))
... hello('world')
... """
>>> class PrintNodeVisitor(ast.NodeVisitor):
... def visit(self, node):
... print(node)
... return super().visit(node)
...
>>> tree = ast.parse(source)
>>> PrintNodeVisitor().visit(tree)
<_ast.Module object at 0x10bec7b38>
<_ast.Import object at 0x10bec7b70>
<_ast.alias object at 0x10bec7ba8>
<_ast.FunctionDef object at 0x10bec7c18>
<_ast.arguments object at 0x10bec7c50>
<_ast.arg object at 0x10bec7c88>
<_ast.Expr object at 0x10bec7d30>
<_ast.Call object at 0x10bec7d68>
<_ast.Name object at 0x10bec7da0>
<_ast.Load object at 0x10bebe0f0>
<_ast.Call object at 0x10bec7e10>
<_ast.Attribute object at 0x10bec7e48>
<_ast.Str object at 0x10bec7e80>
<_ast.Load object at 0x10bebe0f0>
<_ast.Name object at 0x10bec7eb8>
<_ast.Load object at 0x10bebe0f0>
<_ast.Expr object at 0x10bec7f28>
<_ast.Call object at 0x10bec7f60>
<_ast.Name object at 0x10bec7f98>
<_ast.Load object at 0x10bebe0f0>
<_ast.Str object at 0x10bec7fd0>
Ich konnte die Knoten einfach anzeigen, indem ich dem abstrakten Syntaxbaum folgte. Um einen bestimmten Knoten zu verknüpfen, definieren Sie eine Methode für * visit_NodeClassname *.
3.4
>>> class PrintExprNodePisitor(ast.NodeVisitor):
... def visit_Expr(self, node):
... print('Expr is visited')
... return node
...
>>> PrintExprNodePisitor().visit(tree)
Expr is visited
Expr is visited
Wenn Sie es mit der Ausgabe von * PrintNodeVisitor * vergleichen, können Sie sehen, dass es den * Expr * -Knoten zweimal verfolgt.
Beginnen wir mit einem einfachen Quellcode.
3.4
>>> import ast
>>> source = """
... print(s)
... """
>>> s = 'hello world'
>>> code = compile(source, '<string>', 'exec')
>>> exec(code)
hello world
Verwenden Sie * ast.dump *, um zu sehen, in welchen abstrakten Syntaxbaum dieser Quellcode erweitert wird. bestätigen.
3.4
>>> tree = ast.parse(source)
>>> ast.dump(tree)
"Module(body=[Expr(value=Call(func=Name(id='print', ctx=Load()), args=[Name(id='s', ctx=Load())], keywords=[], starargs=None, kwargs=None))])"
Lassen Sie uns dabei ein geeignetes Beispiel betrachten.
In diesem Beispiel wird die Ausgabezeichenfolge invertiert. Es scheint viele Möglichkeiten zu geben, dies zu tun, aber ich werde versuchen, die Anweisung * print * durch eine andere Funktion zu ersetzen.
3.4
>>> class ReversePrintNodeTransformer(ast.NodeTransformer):
... def visit_Name(self, node):
... if node.id == 'print':
... name = ast.Name(id='reverse_print', ctx=ast.Load())
... return ast.copy_location(name, node)
... return node
...
>>> def reverse_print(s):
... print(''.join(reversed(s)))
...
>>> code = compile(ReversePrintNodeTransformer().visit(tree), '<string>', 'exec')
>>> exec(code)
dlrow olleh
>>> s = 'revese print'
>>> exec(code)
tnirp esever
Es hat so funktioniert. Die Anweisung * print * wurde durch die Funktion * reverse_print * ersetzt und wird ausgeführt.
Verwenden Sie * ast.copy_location *, um * lineno * und * col_offset * vom ursprünglichen Knoten zu kopieren. Ich werde. Sie können ein AST-Objekt ohne diese beiden Attribute nicht * kompilieren *.
Versuchen wir ein Beispiel, das fehlschlägt.
3.4
>>> from ast import *
>>> expression_without_attr = dump(parse('1 + 1', mode='eval'))
>>> expression_without_attr
'Expression(body=BinOp(left=Num(n=1), op=Add(), right=Num(n=1)))'
>>> code = compile(eval(expression_without_attr), '<string>', 'eval')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: required field "lineno" missing from expr
Übergeben Sie nun * include_attributes = True *, um die Attribute auch an * ast.dump * auszugeben.
3.4
>>> expression_with_attr = dump(parse('1 + 1', mode='eval'), include_attributes=True)
>>> expression_with_attr
'Expression(body=BinOp(left=Num(n=1, lineno=1, col_offset=0), op=Add(), right=Num(n=1, lineno=1, col_offset=4), lineno=1, col_offset=0))'
>>> code = compile(eval(expression_with_attr), '<string>', 'eval')
>>> eval(code)
2
Es war auch möglich, ein AST-Objekt aus der Ausgabe von * ast.dump * zu generieren (auszuwerten), indem * lineno * oder * col_offset * ausgegeben und kompiliert wurden.
Eine andere Lösung ist die Verwendung von * ast.fix_missing_locations *. Versuchen wir früher, * expression_without_attr * zu verwenden.
3.4
>>> code = compile(fix_missing_locations(eval(expression_without_attr)), '<string>', 'eval')
>>> eval(code)
2
Jetzt können Sie * kompilieren *. Gemäß der Dokumentation * fix_missing_locations *
Das Ausfüllen dieser Knoten in die generierten Knoten ist eine ziemlich mühsame Aufgabe, daher setzt dieser Helfer rekursiv dieselben Werte wie der übergeordnete Knoten auf diejenigen, für die die beiden Attribute nicht festgelegt sind. ..
Es scheint, dass es automatisch eingestellt wird.
Es ist ein wenig schwierig, mit dem abstrakten Syntaxbaum zu spielen, um das Problem zu finden, das Sie lösen möchten, aber hier sind einige der Dinge, die ich gefunden habe.
Es kann nützlich sein, sich daran zu erinnern, wenn Python-Code als Daten behandelt wird, dh wenn es schwierig ist, ohne ihn auszukommen.
Recommended Posts