¿Cómo ensamblar árboles de sintaxis abstracta (AST) de Python en Mathematica?
Me gustaría ensamblar objetos Python ' ast.Module ' dentro de Mathematica y luego enviarlos a Python vía eg ExternalEvaluate["exec(astObject)"].
Las funciones Python ast , parse , eval , exec y compile pueden operar en objetos ' ast.Module ', ya sea emitiéndolos o tomándolos como entradas. Sin embargo, no sé cómo ensamblar esto astObjectdentro 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 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.
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 producir el mismo resultado (es decir, 12).
Sin embargo, quiero crear 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, tengo que descubrir 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 superior .
Originalmente pensé que podría usar una forma de cadena intermedia de la ast.dump()función de Python 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 dado que esto astStringesencialmente 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 esto astString.
¿Es posible crear este tipo de objeto Python, como el astObjectanterior, en el lado de Mathematica?
segundo
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
Respuestas
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 enviarlo 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 de usar ExternalEvaluatey la biblioteca "ast" de Python.
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é en evallugar de exec, porque evaldevuelve un valor).
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 decirlo 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 representar 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 construir 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]
Afuera:
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]
Afuera:
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 "C simbólica" en la documentación de Mathematica. Es básicamente 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).
Volviendo 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.