Come assemblare alberi di sintassi astratta Python (AST) in Mathematica?

Vorrei assemblare Python “ ast.Module “oggetti allinterno di Mathematica e quindi inviarli a Python tramite es ExternalEvaluate["exec(astObject)"].

Il Python ast , parse , eval , exec e le funzioni compile possono tutte operare su “ ast.Module “oggetti, emettendoli o prendendoli come input. Tuttavia non so come assemblare questo astObject allinterno di Mathematica / WL e quindi inviarlo a Python tramite ExternalEvaluate.

Sto cercando di generare a livello di codice codice Python in MMA (per un algoritmo genetico) e quindi inviarlo a Python per la valutazione. Potrei assemblare stringhe di codice Python, ma poi devo gestire tutto il rientro, il che sembra una seccatura.

Ad esempio, in Python è possibile fare quanto segue:

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

E ovviamente da MMA è possibile:

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

per ottenere lo stesso risultato (ad es. 12).

Tuttavia, voglio generare i miei bit di codice Python ("X1=3", "X2=4", "X3=X1*X2") in Mathematica. Questi bit qui sono abbastanza semplici, ma intendo generare programmi completi, cioè dichiarazioni ed espressioni, metaprogrammaticamente (!). Per farlo devo quindi capire come analizzare i fastidiosi rientri di Python, che è ovviamente il modo in cui distingue un insieme di espressioni dal successivo e quali sono le loro dipendenze. Sono restio a farlo e ho pensato che potrebbe essere più facile operare sulla struttura ast .

Inizialmente pensavo di poter utilizzare una forma stringa intermedia dalla funzione Python “s ast.dump() che assomiglia a:

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())))])" 

e da allora astString essenzialmente serializza il astObject Potrei anche generare questo invece. Tuttavia non riesco a trovare alcun modo per convincere Python a fare qualcosa con questo astString.

È possibile creare questo tipo di oggetto Python – come il mio astObject sopra – sul lato Mathematica?

B

PS: ecco una descrizione degli oggetti “ ast.Module “: https://greentreesnakes.readthedocs.io/en/latest/tofrom.html

PPS: ho pubblicato un post incrociato su Wolfram Community: https://community.wolfram.com/groups/-/m/t/2070851

Commenti

  • ” Per farlo devo poi capire come analizzare le fastidiose rientranze di Python ‘ […] ” – So che stai chiedendo di utilizzare AST, ma forse ti interessano anche le risposte che trattano i rientri di Python ‘.
  • Sì, Anton, hai ragione, sto anche cercando soluzioni puramente basate su stringhe. E in questo senso stavo guardando il tuo pacchetto FunctionalParsers.m come un modo per importare automaticamente la grammatica EBNF di Python ‘ per forse permettermi di generare pseudo-Python-con-bracketing esplicito , o forse per generare codice Hy (che è Lisp scritto in Python) che ovviamente ha un corretto bracketing. Non riesco a ‘ capire perché qualcuno creerebbe un linguaggio basato sul rientro o perché altri ‘ continuerebbero a usarlo …
  • ” Non riesco a ‘ capire perché qualcuno dovrebbe creare un linguaggio basato sul rientro o perché altri ‘ continuerebbero a usarlo … ” – LOL! Molte persone ne sono disorientate!
  • Usare ” FunctionalParsers.m ” è unidea interessante, ma perseguirla potrebbe essere tuttaltro che unimpresa facile …
  • Potrebbe essere meglio programmare due monadi software, una in WL e unaltra in Python, che hanno lo stesso design del flusso di lavoro. Quindi trasferisci tra di loro con piccole modifiche al codice; inoltre non è necessaria alcuna formattazione speciale per Python. Ecco un esempio con una monade chiamata LSAMon . (Scorri fino in fondo.)

Risposta

Non sono sicuro di cosa stai cercando esattamente. Penso che la risposta alla tua domanda:

Sto cercando di generare a livello di codice codice Python in MMA […] e quindi inviarlo a Python per la valutazione. Potrei assemblare stringhe di codice Python, ma poi devo gestire tutto il rientro, il che sembra una seccatura.

È possibile creare questa sorta di oggetto Python sul lato Mathematica?

è piuttosto semplice usare ExternalEvaluate e Python “s ” ast ” libreria.

Ecco un esempio:

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} *) 

(ho usato eval invece di exec, perché eval restituisce un valore.)

Commenti

  • Grazie Anton. Sì, avrei potuto essere più chiaro. Permettimi di aggiornare un po la mia domanda. Fondamentalmente voglio generare il ” ‘ [i ** 2 for i in range (10)] ‘ ” parte nella tua risposta sul lato MMA, ma non come una stringa Python, ma come loggetto ast.Module che può essere inserito in compile, eval ed exec.

Risposta

Penso che compilare codice Python come una stringa sia in realtà più semplice di quello che proponi. Ma so anche che dico solo che non “convincerà nessuno, quindi ecco un esempio.

Definiamo un paio di teste simboliche che rappresenteranno il nostro programma Python in Mathematica e una funzione per il rendering espressioni con quelle teste simboliche:

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 ] 

Ciò che queste funzioni ci consentono di fare è scrivere codice Python in Mathematica senza pensare al rientro, “è un po come costruire codice Python con il modulo ast.

Questo è un esempio di rendering dellespressione simbolica come una stringa:

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] 

Out:

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

Possiamo facilmente costruire su questo e rendere più descrittiva la nostra rappresentazione simbolica del programma Python.

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 queste definizioni di supporto, ora possiamo scrivere il seguente programma in uno stile molto leggibile.

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] 

Fuori:

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) 

Se non lhai ancora fatto, cerca ” C simbolico nella documentazione di Mathematica. È fondamentalmente un modo per creare un AST per un programma in linguaggio C in Mathematica che può quindi essere convertito in codice C eseguibile. Fondamentalmente è qui che ci stiamo dirigendo anche con questo codice, anche se se intendessi realizzare unimplementazione completa del genere non sarebbe esattamente così (vale sicuramente la pena studiare il modulo ast se si vuole seguire la strada).

Tornando al punto: quello che voglio trasmettere con questa risposta è che non devi spendere molto tempo per costruire un piccolo framework che risolva più o meno il problema di indentazione che hai menzionato nella tua domanda .

Commenti

  • Sì CE questo assomiglia molto a quello che immaginavo sarebbe stato una tortura da implementare, e hai ragione, ce lhai fatta abbastanza semplice. Dovrò continuare in questo modo estendendo il linguaggio verso il basso verso le foglie per coprire atomi e operatori, liste e tuple e così via, ma questo framework gestisce tutto il rientro e dovrebbe essere fattibile.
  • @berniethejet In questo esempio, ho definito ad esempio PyIf qualcosa che restituisce qualcosaltro. Puoi invece considerare di aggiungere la definizione a ToPythonString, ovvero ToPythonString[PyIf[...]] :=. In questo modo puoi ispezionare lintero programma in forma simbolica, senza che venga valutato fino a quando non chiami ToPythonString.
  • Grazie, buono a sapersi. Il tuo framework è sicuramente migliore di qualsiasi altro avrei messo insieme (brontolando per tutto il tempo, come inevitabilmente sarei, infastidito dalla ‘ crociata di indentazione di Guido).

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *