Comment assembler des arbres de syntaxe abstraite Python (AST) dans Mathematica?
Je voudrais assembler des objets Python ' ast.Module ' à l'intérieur de Mathematica, puis les soumettre à Python via par exemple ExternalEvaluate["exec(astObject)"].
Les fonctions Python ast , parse , eval , exec et compile peuvent toutes opérer sur des objets « ast.Module », soit en les générant soit en les prenant comme entrées. Cependant, je ne sais pas comment assembler cela astObjectdans Mathematica / WL et l'envoyer ensuite à Python via ExternalEvaluate.
J'essaie de générer par programme du code Python en MMA (pour un algorithme génétique), puis de le soumettre à Python pour évaluation. Je pourrais assembler des chaînes de code Python, mais je dois ensuite gérer toute l'indentation, ce qui semble être une douleur.
Par exemple, en Python, il est possible de faire ce qui suit:
import ast
pythonString="X3=X1*X2"
astObject=ast.parse(pythonString)
X1=3
X2=4
exec(compile(astObject,"","exec"))
print(X3)
-> 12
Et bien sûr, à partir de MMA, il est possible de faire:
session=StartExternalSession["Python"]
ExternalEvaluate[session, {"import ast","X1=3","X2=4",
"exec(compile(ast.parse(\"X3=X1*X2\"),\"\",\"exec\"))"} ]
pour donner le même résultat (c'est-à-dire 12).
Cependant , je veux générer mes morceaux de code Python ( "X1=3", "X2=4", "X3=X1*X2") dans Mathematica. Ces bits ici sont assez simples, mais j'ai l'intention de générer des programmes complets, c'est-à-dire des déclarations et des expressions, de manière métaprogrammatique (!). Pour ce faire, je dois ensuite comprendre comment analyser les indentations ennuyeuses de Python, ce qui est bien sûr comment il distingue un ensemble d'expressions du suivant et quelles sont leurs dépendances. Je répugne à le faire et j'ai pensé qu'il serait peut-être plus facile d'opérer sur la structure ast .
À l'origine, j'avais pensé que je pourrais être en mesure d'utiliser une forme de chaîne intermédiaire de la ast.dump()fonction de Python qui ressemble à:
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())))])"
et puisque cela astStringsérialise essentiellement le astObjectje pourrais également générer ceci à la place. Cependant, je ne trouve aucun moyen pour que Python fasse quoi que ce soit avec cela astString.
Est-il possible de créer ce type d'objet Python - comme mon astObjectci-dessus - du côté Mathematica?
B
PS: Voici une description des objets ' ast.Module ':https://greentreesnakes.readthedocs.io/en/latest/tofrom.html
PPS: J'ai posté ceci sur Wolfram Community: https://community.wolfram.com/groups/-/m/t/2070851
Réponses
Je ne sais pas exactement ce que vous recherchez. Je pense que la réponse à votre question:
J'essaie de générer par programmation du code Python en MMA [...] puis de le soumettre à Python pour évaluation. Je pourrais assembler des chaînes de code Python, mais ensuite je dois gérer toute l'indentation, ce qui semble être une douleur.
Est-il possible de créer ce type d'objet Python côté Mathematica?
est assez simple en utilisant ExternalEvaluatela bibliothèque "ast" de Python.
Voici un exemple:
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} *)
(J'ai utilisé à la evalplace de exec, car evalrenvoie une valeur.)
Je pense que compiler du code Python sous forme de chaîne est en fait plus simple que ce que vous proposez. Mais je sais aussi que le simple fait de dire que cela ne convaincra personne, alors voici un exemple.
Nous définissons quelques têtes symboliques qui représenteront notre programme Python dans Mathematica, et une fonction pour rendre les expressions avec ces têtes symboliques:
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
]
Ce que ces fonctions nous permettent de faire, c'est d'écrire du code Python en Mathematica sans penser à l'indentation, c'est un peu comme construire du code Python avec le module ast.
Voici un exemple de rendu de l'expression symbolique sous forme de chaîne:
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]
En dehors:
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')
Nous pouvons facilement construire sur cela et rendre notre représentation symbolique du programme Python plus descriptive.
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
]
}
Avec ces définitions d'aide, nous pouvons maintenant écrire le programme suivant dans un style très lisible.
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]
En dehors:
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 vous ne l'avez pas encore fait, veuillez rechercher "C symbolique" dans la documentation Mathematica. C'est essentiellement un moyen de construire un AST pour un programme en langage C dans Mathematica qui peut ensuite être converti en code C exécutable. C'est essentiellement là où nous nous dirigeons avec ce code, bien que si j'avais l'intention de faire une implémentation complète comme celle-ci, cela ne ressemblerait pas exactement à ceci (le module ast vaut certainement la peine d'être étudié si l'on veut suivre la voie).
Revenons au point: ce que je veux transmettre avec cette réponse, c'est que vous n'avez pas à passer beaucoup de temps pour construire un petit cadre qui résout plus ou moins le problème d'indentation que vous évoquez dans votre question.