¿Cómo ensamblar árboles de sintaxis abstracta (AST) de Python en Mathematica?

Me gustaría ensamblar Python « ast.Module «objetos dentro de Mathematica y luego envíelos a Python vía eg ExternalEvaluate["exec(astObject)"].

Python ast , analizar , eval , exec y compilan todas las funciones pueden operar en « ast.Module «, ya sea emitiéndolos o tomándolos como entradas. Sin embargo, no sé cómo ensamblar este astObject dentro de Mathematica / WL y luego enviarlo a Python a través de ExternalEvaluate.

Estoy tratando de generar código Python mediante programación en MMA (para un algoritmo genético) y luego enviarlo a Python para la evaluación. Podría ensamblar cadenas de código Python, pero luego tengo que manejar toda la sangría, lo que parece una molestia.

Por ejemplo, en Python es posible hacer lo siguiente:

import ast pythonString="X3=X1*X2" astObject=ast.parse(pythonString) X1=3 X2=4 exec(compile(astObject,"","exec")) print(X3) -> 12 

Y por supuesto desde MMA es posible hacer:

session=StartExternalSession["Python"] ExternalEvaluate[session, {"import ast","X1=3","X2=4", "exec(compile(ast.parse(\"X3=X1*X2\"),\"\",\"exec\"))"} ] 

para obtener el mismo resultado (es decir, 12).

Sin embargo, quiero generar mis bits de código Python ("X1=3", "X2=4", "X3=X1*X2") en Mathematica. Estos bits aquí son bastante simples, pero tengo la intención de generar programas completos, es decir, declaraciones y expresiones, metaprogramáticamente (!). Para hacer eso, entonces tengo que descubra cómo analizar las molestas sangrías de Python, que es por supuesto cómo distingue un conjunto de expresiones del siguiente y cuáles son sus dependencias. Soy reacio a hacerlo, y pensé que podría ser más fácil operar en la estructura ast .

Originalmente pensé que podría usar una forma de cadena intermedia de Python «s ast.dump() función que se ve así:

astString = ast.dump(pythonString) -> "Module(Body=[Assign(targets=[Name(id="X3",ctx=Store())],value=BinOp(left=Name(id="X1", ctx=Load()),op=Mult(),right=Name(id="X2",ctx=Load())))])" 

y desde esta astString esencialmente serializa el astObject También podría generar esto en su lugar. Sin embargo, no puedo encontrar ninguna forma de hacer que Python haga algo con este astString.

¿Es posible crear este tipo de objeto Python, como mi astObject arriba, en el lado de Mathematica?

B

PD: Aquí hay una descripción de los objetos « ast.Module «: https://greentreesnakes.readthedocs.io/en/latest/tofrom.html

PPS: He publicado esto en Wolfram Community: https://community.wolfram.com/groups/-/m/t/2070851

Comentarios

  • » Para hacer eso, entonces tengo que averiguar cómo analizar Python ‘ s molestas sangrías […] » – Sé que está pidiendo que se use AST, pero tal vez las respuestas relacionadas con las sangrías de Python ‘ también sean de su interés.
  • Sí, Anton, tienes razón, también estoy buscando soluciones puramente basadas en cadenas. Y en ese sentido, estaba viendo su paquete FunctionalParsers.m como una forma de importar automáticamente la gramática EBNF de Python ‘ para tal vez permitirme generar pseudo-Python-con-corchetes-explícitos , o quizás generar código Hy (que es Lisp escrito en Python) que por supuesto tiene el corchete adecuado. No puedo ‘ entender por qué alguien crearía un idioma basado en la sangría, o por qué otros ‘ seguirían usándolo …
  • » No puedo ‘ entender por qué alguien crearía un lenguaje basado en sangría, o por qué otros ‘ s continuarían usándolo … » – ¡LOL! ¡Mucha gente está desconcertada por eso!
  • Usar » FunctionalParsers.m » es una idea interesante, pero seguirla podría estar lejos de ser una tarea fácil …
  • Puede que sea mejor programar dos mónadas de software, una en WL y otra en Python, que tengan el mismo diseño de flujo de trabajo. Luego, transfiere entre ellos con cambios de código menores; tampoco se necesita un formato especial para Python. Aquí hay un ejemplo con una mónada llamada LSAMon . (Desplácese hasta el final.)

Responder

No estoy seguro de qué es exactamente lo que está buscando. Creo que la respuesta a tu pregunta:

Estoy intentando generar código Python mediante programación en MMA […] y luego envíelo a Python para su evaluación. Podría ensamblar cadenas de código Python, pero luego tengo que manejar toda la sangría, lo que parece una molestia.

¿Es posible crear este tipo de objeto Python en el lado de Mathematica?

es bastante sencillo con ExternalEvaluate y Python «s » ast » biblioteca.

Aquí hay un ejemplo:

code = ""[i**2 for i in range(10)]""; astTemplate = StringTemplate["import ast; eval(compile(ast.parse(`1`, mode="eval"), "", "eval"))"]; astTemplate[code] (* "import ast; eval(compile(ast.parse("[i**2 for i in range(10)]", mode="eval"), "", "eval"))" *) ExternalEvaluate["Python", astTemplate[code]] (* {0, 1, 4, 9, 16, 25, 36, 49, 64, 81} *) 

(usé eval en lugar de exec, porque eval devuelve un valor.)

Comentarios

  • Gracias Anton. Sí, podría haber sido más claro. Permítanme actualizar un poco mi pregunta. Básicamente, quiero generar el » ‘ [i ** 2 para i en el rango (10)] ‘ » parte de su respuesta en el lado de MMA, pero no como una cadena de Python, sino como el objeto ast.Module que puede conectarse a compilar y evaluar y ejecutar.

Respuesta

Creo que compilar código Python como una cadena es en realidad más simple de lo que propones. Pero también sé que solo digo que no convencerá a nadie, así que aquí hay un ejemplo.

Definimos un par de cabezas simbólicas que representarán nuestro programa Python en Mathematica, y una función para renderizar expresiones con esas cabezas simbólicas:

ToPythonString[statements_List] := StringRiffle[ToPythonString /@ statements, "\n"] ToPythonString[PyBlock[statement_, children_, indent_ : 0]] := StringJoin[ StringRepeat[" ", indent], statement, ":\n", ToPythonString[PyIndent /@ children] ] ToPythonString[PyStatement[statement_, indent_ : 0]] := StringJoin[ StringRepeat[" ", indent], statement ] PyIndent[PyBlock[statement_, children_, indent_ : 0]] := PyBlock[ statement, PyIndent /@ children, indent + 1 ] PyIndent[PyStatement[statement_, indent_ : 0]] := PyStatement[ statement, indent + 1 ] 

Lo que estas funciones nos permiten hacer es escribir código Python en Mathematica sin pensar en la sangría, es un poco como compilando código Python con el módulo ast.

Este es un ejemplo de cómo representar la expresión simbólica como una cadena:

prog = { PyStatement["a = 1"], PyStatement["b = 2"], PyBlock["If a > b", { PyStatement["Print("a is larger than b")"] }], PyBlock["def f(x)", { PyStatement["Print("executing f")"], PyBlock["if x > 0", { PyStatement["Print("x is larger than 0")"] }] }] }; ToPythonString[prog] 

Fuera:

a = 1 b = 2 If a > b: Print("a is larger than b") def f(x): Print("executing f") if x > 0: Print("x is larger than 0") 

Podemos construir fácilmente sobre esto y hacer que nuestra representación simbólica del programa Python sea más descriptiva.

PyAssign[lhs_, rhs_] := PyStatement[lhs <> " = " <> rhs] PyPrint[text_] := PyStatement["Print(" <> text <> ")"] PyFunction[name_, args_, statements_] := PyBlock[ "def " <> name <> "(" <> StringRiffle[args, ", "] <> ")", statements ] PyIf[cond_, statements_] := PyBlock[ "If " <> cond, statements ] PyIf[cond_, statements_, elseStatements_] := { PyBlock[ "If " <> cond, statements ], PyBlock[ "else", elseStatements ] } 

Con estas definiciones de ayuda, ahora podemos escribir el siguiente programa en un estilo muy legible.

prog = { PyAssign["a", "1"], PyAssign["b", "2"], PyIf[ "a > b", { PyPrint["a is larger than b"] }], PyFunction["f", {"x"}, PyIf[ "x > 0", {PyPrint["x is larger than 0"]}, {PyPrint["x is not larger than 0"]} ] ] }; ToPythonString[prog] 

Fuera:

a = 1 b = 2 If a > b: Print(a is larger than b) def f(x): If x > 0: Print(x is larger than 0) else: Print(x is not larger than 0) 

Si aún no lo ha hecho, busque » simbólico C en la documentación de Mathematica. Básicamente es una forma de construir un AST para un programa en lenguaje C en Mathematica que luego se puede convertir en código C ejecutable. Eso es básicamente hacia donde nos dirigimos con este código también, aunque si tuviera la intención de hacer una implementación completa como esa, no se vería exactamente así (el módulo ast ciertamente vale la pena estudiarlo si uno quiere seguir el camino).

De vuelta al grano: lo que quiero transmitir con esta respuesta es que no tienes que dedicar mucho tiempo a construir un marco pequeño que resuelva más o menos el problema de sangría que mencionas en tu pregunta .

Comentarios

  • Sí CE, esto se parece mucho a lo que estaba imaginando que sería una tortura de implementar, y tienes razón, lo has logrado bastante sencillo. Tendré que continuar de esta manera extendiendo el lenguaje hacia abajo hacia las hojas para cubrir átomos y operadores y listas y tuplas y demás, pero este marco maneja toda la sangría, y debería ser factible.
  • @berniethejet En este ejemplo, definí, por ejemplo, PyIf como algo que se evalúa en otra cosa. En su lugar, puede considerar agregar la definición a ToPythonString, es decir, ToPythonString[PyIf[...]] :=. De esa manera, puede inspeccionar todo su programa en forma simbólica, sin que sea evaluado hasta que llame a ToPythonString.
  • Gracias, es bueno saberlo. Su marco es ciertamente mejor que cualquier otro que hubiera improvisado (refunfuñando todo el camino, como inevitablemente estaría molesto por la cruzada de sangría de Guido ‘).

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *