Erste Schritte mit Pythons Ast-Modul (Verwenden von NodeVisitor)

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.

Folgen Sie dem abstrakten Syntaxbaum mit * NodeVisitor *

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.

Ändern Sie den abstrakten Syntaxbaum mit * NodeTransformer *

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.

Beim Spielen mit einem abstrakten Syntaxbaum (mit * NodeTransformer *)

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

Erste Schritte mit Pythons Ast-Modul (Verwenden von NodeVisitor)
Erste Schritte mit Pythons Ast-Modul (Folgen des abstrakten Syntaxbaums)
1.1 Erste Schritte mit Python
Erste Schritte mit apache2
Erste Schritte mit Python
Erste Schritte mit Django 1
Einführung in die Optimierung
Erste Schritte mit Numpy
Erste Schritte mit Spark
Erste Schritte mit Pydantic
Erste Schritte mit Jython
Erste Schritte mit Django 2
Übersetzen Erste Schritte mit TensorFlow
Einführung in Python-Funktionen
Einführung in Tkinter 2: Button
Erste Schritte mit Go Assembly
Erste Schritte mit Python Django (4)
Erste Schritte mit Python Django (3)
Erste Schritte mit Django mit PyCharm
Erste Schritte mit Python Django (5)
Erste Schritte mit Python3 # 3 Versuchen Sie erweiterte Berechnungen mit der import-Anweisung
Erste Schritte mit Python Responder v2
Einführung in Git (1) History-Speicher
Erste Schritte mit Sphinx. Generieren Sie Docstring mit Sphinx
Erste Schritte mit Python-Webanwendungen
Erste Schritte mit Python für PHPer-Klassen
Erste Schritte mit Sparse Matrix mit scipy.sparse
Erste Schritte mit Python Grundlagen von Python
Sortieren Sie Namen mit dem Zufallsmodul von Python
Erste Schritte mit der Cisco Spark REST-API
Beginnend mit USD unter Windows
Erfahren Sie mehr über die Protokollierung mit dem Python-Protokollierungsmodul ①
Erste Schritte mit genetischen Python-Algorithmen
Erste Schritte mit Python 3.8 unter Windows
Versuchen Sie, Pythons networkx mit AtCoder zu verwenden
Erste Schritte mit Python für PHPer-Funktionen
Erste Schritte mit der CPU-Diebstahlzeit
Erste Schritte mit Python3 # 1 Grundkenntnisse erlernen
Erste Schritte mit Python Web Scraping Practice
Versuchen Sie, die Kamera mit Pythons OpenCV zu verwenden
Erste Schritte mit Python Web Scraping Practice
Erste Schritte mit Lisp für Pythonista: Ergänzung
Erste Schritte mit Heroku, Bereitstellen der Flaschen-App
Bearbeiten Sie WAV-Dateien mit dem Wave-Modul von Python
Erste Schritte mit TDD mit Cyber-dojo bei MobPro
Erste Schritte mit Python mit 100 Klopfen bei der Sprachverarbeitung
MongoDB-Grundlagen: Erste Schritte mit CRUD mit JAVA
Erste Schritte mit dem Zeichnen mit matplotlib: Schreiben einfacher Funktionen
Erste Schritte mit der japanischen Übersetzung des Keras Sequential-Modells
Django Erste Schritte Teil 2 mit dem Eclipse Plugin (PyDev)
Erste Schritte mit AWS IoT in Python
Materialien zum Lesen, wenn Sie mit Python beginnen
Einstellungen für den Einstieg in MongoDB mit Python
Grale fangen an
Erste Schritte mit Python3 # 2 Erfahren Sie mehr über Typen und Variablen
Erste Schritte mit Pandas: Grundkenntnisse, an die Sie sich zuerst erinnern sollten