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