[PYTHON] Exemple de réécriture de code par ast.NodeTransformer

Ce n'est pas un problème d'écrire un convertisseur Je vais écrire un petit contenu d'essai. Pour rappel, je touche rarement avec Python.

Ce que je voulais faire

Par exemple, je souhaite générer une fonction qui double l'argument v et le renvoie lorsque la chaîne de caractères «" v * 2 "» est donnée. (On sait que le nom de l'argument est v)

Politique de réalisation

Pour déclaration unique

Comme dans l'exemple ci-dessus, si la chaîne donnée est une instruction unique, c'est très simple, utilisez simplement une opération de chaîne pour en faire un lambda et l'évaluez.

single.py


text = "v * 2"
func = eval("lambda v:" + text)

Prise en charge de plusieurs déclarations

La méthode ci-dessus ne peut pas être utilisée si la chaîne donnée peut contenir plusieurs expressions. Par exemple

from System.Windows import Thickness
Thickness(v, 0, 0, 0)

Considérez le cas où est spécifié. Pour le moment, la seule chose qui me vient à l'esprit est d'utiliser une opération de chaîne pour créer une fonction def, puis de l'exécuter. (Ce code ne renvoie None que pour la fonction générée, mais j'en parlerai plus tard)

multi.py


text = """
from System.Windows import Thickness
Thickness(v, 0, 0, 0)
"""
import re
source = "def test(v):\n" + re.sub("^", "  ", text, flags=re.M)
exec(source)
return test

C'est très bien dans la plupart des cas, mais par exemple, lorsqu'un littéral multiligne est donné, il y a un problème que le contenu du littéral est indenté par le traitement de retrait simple par l'expression régulière ci-dessus. ..

"""value of v is:
{v}""".format(v=v)

C'est un cas rare que vous n'avez pas vraiment besoin de considérer, mais c'est un peu inconfortable de savoir qu'il y a un mauvais modèle, donc comme alternative, nous en ferons une fonction par conversion ast.

  1. Premièrement, générez un ast pour une fonction vide def appelée def test (v): pass.
  2. Remplacez la partie Noeud de passage de l'ast généré par le noeud ast généré à partir de la chaîne de caractères donnée.

La procédure.

Cela ressemble à ceci lorsqu'il est écrit dans le code.

functionize.py


import ast

class _MakeFunction(ast.NodeTransformer):
    def __init__(self, body):
        self.body = body

    def run(self):
        template_ast = ast.parse('def test(v): pass', mode='exec')
        return self.visit(template_ast)
 
    def visit_Pass(self, node):
        return ast.parse(self.body, mode='exec').body
 
if __name__ == '__main__':
    body = "from System.Windows import Thickness\nThickness(v, 0, 0, 0)"
    functionized_ast = _MakeFunction(body).run()
    exec(compile(functionized_ast, '<string>', mode='exec'))

Génération de valeur de retour

Maintenant, c'est une fonction, mais comme je l'ai écrit plus tôt, il n'y a pas de valeur de retour en l'état. Donc, à l'aide de la conversion ast, réécrivez-la pour que le résultat de la dernière expression évaluée soit renvoyé comme valeur de retour comme Ruby. La procédure est la suivante.

  1. Ajoutez le code _retval_ = None au début de la fonction
  2. Ajoutez le code return _retval_ à la fin de la fonction
  3. Réécrivez toutes les expressions qui apparaissent dans la fonction avec l'instruction d'affectation pour _retval_.

Écrivons un Transformer qui fait cela dans le code.

functionize.py



class _ReturnLastResult(ast.NodeTransformer):
    def visit_FunctionDef(self, node):
        self.generic_visit(node)
        retval_assign = \
                ast.Assign(targets=[ast.Name(id='_retval_', ctx=ast.Store())],
                           value=ast.NameConstant(value=None))               
        retval_return = \
                ast.Return(value=ast.Name(id='_retval_', ctx=ast.Load()))
        node.body.insert(0, retval_assign)
        node.body.append(retval_return)
        return ast.fix_missing_locations(node)

    def visit_Expr(self, node):
        target = ast.Name(id='_retval_', ctx=ast.Store())
        assign = ast.copy_location(
                    ast.Assign(targets=[target], value=node.value), node)
        return assign

code

Le code final ressemble à ceci. https://gist.github.com/wonderful-panda/8a22b74248a60cc8bb22

Après tout, j'ai décidé de ne considérer que la déclaration unique dans l'entrée d'origine et je ne l'ai pas utilisée.

Recommended Posts

Exemple de réécriture de code par ast.NodeTransformer
Exemple d'analyse de squelette tridimensionnelle par Python
Expliquez le code de Tensorflow_in_ROS
Visualisation des données par préfecture
2.x, 3.x code de caractères des séries python
Enregistrement du code des études cliniques rejeté par le comité d'éthique
Bases de la théorie de l'information quantique: opération logique par code de surface (Brading)