Premiers pas avec le module ast de Python (à l'aide de NodeVisitor)

Dans la précédente Introduction au module ast de Python (suivant l'arbre de syntaxe abstraite), je vous ai présenté pour suivre l'arbre de syntaxe abstraite en utilisant la fonction d'assistance du module ast. fait.

Suivez l'arborescence de syntaxe abstraite avec * NodeVisitor *

Une façon consiste à utiliser la fonction d'assistance du module ast, mais si vous utilisez * ast.NodeVisitor *, vous pouvez faire plus. Vous pouvez facilement suivre l'arborescence de la syntaxe abstraite. Il est plus facile de comprendre que ce que vous faites est la même chose que d'utiliser une fonction d'assistance en regardant l'implémentation de * NodeVisitor *, donc je vais l'introduire à partir de cette implémentation. * NodeVisitor * est l'un des modèles de conception appelés Modèle de visiteur.

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)

Si * visit_NodeClassname * n'est pas défini, * ast.iter_fields * suivra l'arborescence de syntaxe abstraite * generic_visit * Il sera exécuté. La classe de noeuds de l'arborescence de syntaxe abstraite prend * ast.AST * comme classe de base, donc * isinstance (valeur, AST) ) * Détermine s'il s'agit d'une instance de nœud et la parcourt récursivement (* self.visit () *).

Utilisons-le réellement. Définit une classe qui hérite de * NodeVisitor *.

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>

J'ai pu facilement visualiser les nœuds en suivant l'arborescence de syntaxe abstraite. Pour accrocher un nœud particulier, définissez une méthode pour * 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

Si vous le comparez avec la sortie de * PrintNodeVisitor *, vous pouvez voir qu'il trace le nœud * Expr * deux fois.

Modifiez l'arborescence de la syntaxe abstraite avec * NodeTransformer *

Commençons par un simple code source.

3.4


>>> import ast
>>> source = """
... print(s)
... """
>>> s = 'hello world'
>>> code = compile(source, '<string>', 'exec')
>>> exec(code)
hello world

Utilisez * ast.dump * pour voir dans quel arbre de syntaxe abstraite ce code source est développé. confirmer.

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))])"

Tout en regardant cela, pensons à un exemple approprié.

Ici, à titre d'exemple, inversons la chaîne de caractères de sortie. Il semble y avoir plusieurs façons de faire cela, mais je vais essayer de remplacer l'instruction * print * par une autre fonction.

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

Cela a fonctionné comme ça. L'instruction * print * a été remplacée par la fonction * reverse_print * et est en cours d'exécution.

Utilisez * ast.copy_location * pour copier * lineno * et * col_offset * à partir du nœud d'origine. Je vais. Vous ne pouvez pas * compiler * un objet AST sans ces deux attributs.

Essayons un exemple qui échoue.

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

Passez maintenant * include_attributes = True * pour afficher également les attributs dans * ast.dump *.

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

Il était également possible de générer (eval) un objet AST tel qu'il est à partir de la sortie de * ast.dump * en sortant * lineno * ou * col_offset * et en le compilant.

Une autre solution consiste à utiliser * ast.fix_missing_locations *. Essayons d'utiliser * expression_without_attr * plus tôt.

3.4


>>> code = compile(fix_missing_locations(eval(expression_without_attr)), '<string>', 'eval')
>>> eval(code)
2

Vous pouvez maintenant * compiler *. Selon la documentation de * fix_missing_locations *

Les remplir dans les nœuds générés est une tâche assez fastidieuse, donc cet assistant définit de manière récursive les mêmes valeurs que le nœud parent sur celles qui n'ont pas les deux attributs définis. ..

Il semble qu'il sera réglé automatiquement.

Lorsque vous jouez avec un arbre de syntaxe abstrait (en utilisant * NodeTransformer *)

Il est un peu difficile de jouer avec l'arborescence de syntaxe abstraite pour trouver le problème que vous voulez résoudre, mais voici quelques-unes des choses que j'ai trouvées.

Il peut être utile de se souvenir quand on traite du code Python en tant que données, c'est-à-dire quand il y a quelque chose de difficile à faire sans.

Recommended Posts

Premiers pas avec le module ast de Python (à l'aide de NodeVisitor)
Premiers pas avec le module ast de Python (en suivant l'arborescence de la syntaxe abstraite)
1.1 Premiers pas avec Python
Premiers pas avec apache2
Premiers pas avec Python
Premiers pas avec Django 1
Introduction à l'optimisation
Premiers pas avec Numpy
Premiers pas avec Spark
Premiers pas avec Pydantic
Premiers pas avec Jython
Premiers pas avec Django 2
Traduire Premiers pas avec TensorFlow
Introduction aux fonctions Python
Introduction à Tkinter 2: Button
Premiers pas avec Go Assembly
Premiers pas avec Python Django (4)
Premiers pas avec Python Django (3)
Premiers pas avec Django avec PyCharm
Premiers pas avec Python Django (5)
Premiers pas avec python3 # 3 Essayez des calculs avancés à l'aide de l'instruction d'importation
Premiers pas avec Python responder v2
Introduction à Git (1) Stockage d'historique
Premiers pas avec Sphinx. Générer docstring avec Sphinx
Premiers pas avec les applications Web Python
Premiers pas avec Python pour les classes PHPer
Premiers pas avec Sparse Matrix avec scipy.sparse
Premiers pas avec Python Bases de Python
Trier les noms avec le module aléatoire de Python
Premiers pas avec Cisco Spark REST-API
Commençant par USD sur Windows
En savoir plus sur la journalisation à l'aide du module de journalisation de Python ①
Premiers pas avec les algorithmes génétiques Python
Premiers pas avec Python 3.8 sous Windows
Essayez d'utiliser le networkx de Python avec AtCoder
Premiers pas avec Python pour les fonctions PHPer
Premiers pas avec CPU Steal Time
Premiers pas avec python3 # 1 Apprenez les connaissances de base
Premiers pas avec Python Web Scraping Practice
Essayez d'utiliser l'appareil photo avec OpenCV de Python
Premiers pas avec Python Web Scraping Practice
Premiers pas avec Lisp pour Pythonista: Supplément
Premiers pas avec Heroku, déploiement de l'application Flask
Modifier les fichiers wav à l'aide du module Wave de Python
Premiers pas avec TDD avec Cyber-dojo chez MobPro
Démarrer avec Python avec 100 coups sur le traitement du langage
Principes de base de MongoDB: Premiers pas avec CRUD avec JAVA
Premiers pas avec le dessin avec matplotlib: écrire des fonctions simples
Premiers pas avec la traduction japonaise du modèle séquentiel Keras
Django Getting Started Part 2 avec eclipse Plugin (PyDev)
Premiers pas avec AWS IoT facilement en Python
Matériel à lire lors de la mise en route de Python
Paramètres pour démarrer avec MongoDB avec python
Grails pour commencer
Premiers pas avec python3 # 2 En savoir plus sur les types et les variables
Premiers pas avec les pandas: connaissances de base à retenir en premier