[PYTHON] Beispiel für das Umschreiben von Code durch ast.NodeTransformer

Es ist kein Problem, einen Konverter zu schreiben Ich werde einen kleinen Testinhalt schreiben. Zur Erinnerung, ich berühre Python selten.

Was ich machen wollte

Wenn beispielsweise die Zeichenfolge "v * 2" angegeben wird, möchte ich eine Funktion generieren, die das Argument v verdoppelt und zurückgibt. (Es ist bekannt, dass der Name des Arguments v ist)

Realisierungspolitik

Für einzelne Aussage

Wie im obigen Beispiel ist es sehr einfach, wenn die angegebene Zeichenfolge eine einzelne Anweisung ist. Verwenden Sie einfach eine Zeichenfolgenoperation, um sie zu einem Lambda zu machen und zu bewerten.

single.py


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

Unterstützung für mehrere Anweisungen

Die obige Methode kann nicht verwendet werden, wenn die angegebene Zeichenfolge mehrere Ausdrücke enthalten kann. Zum Beispiel

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

Betrachten Sie den Fall, in dem angegeben ist. Derzeit fällt mir nur ein, eine String-Operation zu verwenden, um eine Funktion def zu erstellen und diese dann auszuführen. (Dieser Code gibt für die generierte Funktion nur None zurück, aber darüber werde ich später sprechen.)

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

Dies ist in den meisten Fällen in Ordnung, aber wenn beispielsweise ein mehrzeiliges Literal angegeben wird, besteht das Problem, dass der Inhalt des Literals durch die einfache Einrückungsverarbeitung durch den obigen regulären Ausdruck eingerückt wird. ..

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

Es ist ein seltener Fall, den Sie nicht wirklich berücksichtigen müssen, aber es ist etwas unangenehm zu wissen, dass es ein schlechtes Muster gibt. Als Alternative machen wir es zu einer Funktion, die durch Ast-Konvertierung defekt ist.

  1. Generieren Sie zunächst einen Ast für eine leere Funktion def mit dem Namen "def test (v): pass".
  2. Ersetzen Sie den Pass-Knoten-Teil des generierten Asts durch den Ast-Knoten, der aus der angegebenen Zeichenfolge generiert wurde.

Der Ablauf.

Es sieht so aus, wenn es in Code geschrieben ist.

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'))

Rückgabewertgenerierung

Jetzt ist es eine Funktion, aber wie ich zuvor geschrieben habe, gibt es keinen Rückgabewert wie er ist. Schreiben Sie die ast-Konvertierung also so um, dass das Ergebnis des zuletzt ausgewerteten Ausdrucks wie Ruby als Rückgabewert zurückgegeben wird. Das Verfahren ist wie folgt.

  1. Fügen Sie am Anfang der Funktion den Code _retval_ = None hinzu
  2. Fügen Sie am Ende der Funktion den Code return _retval_ hinzu
  3. Schreiben Sie alle Ausdrücke, die in der Funktion erscheinen, mit der Zuweisungsanweisung für _retval_ neu.

Schreiben wir einen Transformator, der dies im Code tut.

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

Der endgültige Code sieht so aus. https://gist.github.com/wonderful-panda/8a22b74248a60cc8bb22

Immerhin habe ich mich entschlossen, nur Single Statement im ursprünglichen Eintrag zu berücksichtigen und habe dies nicht verwendet.

Recommended Posts

Beispiel für das Umschreiben von Code durch ast.NodeTransformer
Beispiel einer dreidimensionalen Skelettanalyse von Python
Erläutern Sie den Code von Tensorflow_in_ROS
Visualisierung von Daten nach Präfektur
2.x, 3.x Serienzeichencode von Python
Aufzeichnung des Codes für klinische Studien, die von der Ethikkommission abgelehnt wurden
Grundlagen der Quanteninformationstheorie: Logische Operation durch Oberflächencode (Brading)