Hur monterar man Python-abstrakta syntaxträd (AST) i Mathematica?

Jag vill montera Python ” ast.Module ”föremål inuti Mathematica och sedan skicka dem till Python via t.ex. ExternalEvaluate["exec(astObject)"].

Python ast , analysera , eval , exec och kompilera funktioner kan alla fungera på ” ast.Module ”objekt, antingen matar ut dem eller tar dem som ingångar. Men jag vet inte hur jag monterar det här astObject inom Mathematica / WL och sedan skicka den till Python via ExternalEvaluate.

Jag försöker programmera generera Python-kod i MMA (för en genetisk algoritm) och sedan skicka den till Python för utvärdering. Jag kunde sätta ihop Python-kodsträngar, men då måste jag hantera all indragning, vilket verkar som en smärta.

Till exempel, i Python är det möjligt att göra följande:

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

Och naturligtvis från MMA är det möjligt att göra:

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

för att ge samma resultat (dvs. 12).

Men jag vill generera mina bitar av Python-kod ("X1=3", "X2=4", "X3=X1*X2") i Mathematica. Dessa bitar här är enkla nog, men jag tänker generera kompletta program, dvs uttalanden och uttryck, metaprogrammatiskt (!). För att göra det måste jag sedan ta reda på hur man analyserar Pythons irriterande fördjupningar, vilket naturligtvis är hur det skiljer en uppsättning uttryck från nästa och vad deras beroende är. Jag är ovillig att göra det och tänkte att det skulle vara lättare att använda ast strukturen.

Ursprungligen trodde jag att jag skulle kunna använda en mellanliggande strängform från Python ”s ast.dump() -funktion som ser ut som:

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

och eftersom detta astString Serierar i huvudsak astObject Jag kan också generera detta istället. Men jag kan inte hitta något sätt att få Python att göra någonting med detta astString.

Är det möjligt att skapa den här typen av Python-objekt – som mitt astObject ovan – på Mathematica-sidan?

B

PS: Här är en beskrivning av ” ast.Module ” -objekt: https://greentreesnakes.readthedocs.io/en/latest/tofrom.html

PPS: Jag har korspostat detta på Wolfram Community: https://community.wolfram.com/groups/-/m/t/2070851

Kommentarer

  • ” För att göra det måste jag ta reda på hur man analyserar Python ’ s irriterande fördjupningar […] ” – Jag vet att du ber om att AST ska användas, men kanske svar som handlar om Python ’ s fördjupningar är också intressanta för dig.
  • Ja, Anton, du har rätt, jag tittar också på rent strängbaserade lösningar. Och i den riktningen såg jag på ditt FunctionalParsers.m-paket som ett sätt att automatiskt importera Python ’ s EBNF-grammatik för att kanske tillåta mig att generera pseudo-Python-med-explicit-parentes , eller för att kanske skapa Hy-kod (som är Lisp skriven i Python) som naturligtvis har rätt parentes. Jag kan bara ’ inte förstå varför någon skulle skapa ett språk baserat på indrag, eller varför andra ’ sedan skulle fortsätta använda det …
  • ” Jag kan bara ’ inte förstå varför någon skulle skapa ett språk baserat på indrag, eller varför andra ’ skulle sedan använda den … ” – LOL! Många människor förväxlas av det!
  • Att använda ” FunctionalParsers.m ” är en intressant idé, men att följa den kan vara långt ifrån ett enkelt åtagande …
  • Det är bättre att programmera två programvarumonader, en i WL och en annan i Python, som har samma arbetsflödesdesign. Sedan överför du mellan dem med mindre kodändringar; det behövs inte heller någon speciell formatering för Python. Här är ett exempel med en monad som heter LSAMon . (Rulla till botten.)

Svar

Jag är inte säker på vad du exakt letar efter. Jag tror att svaret på din fråga:

Jag försöker programmera generera Python-kod i MMA […] och skicka det sedan till Python för utvärdering. Jag kunde sätta ihop Python-kodsträngar, men då måste jag hantera hela indragningen, som verkar som en smärta.

Är det möjligt att skapa den här typen av Python-objekt på Mathematica-sidan?

är ganska enkelt med ExternalEvaluate och Python ”s ” ast ” bibliotek.

Här är ett exempel:

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

(jag använde eval istället för exec, eftersom eval returnerar ett värde.)

Kommentarer

  • Tack Anton. Ja, jag kunde ha varit tydligare. Låt mig uppdatera min fråga lite. I grund och botten vill jag skapa ” ’ [i ** 2 för i inom intervallet (10)] ’ ” del i ditt svar på MMA-sidan, men inte som en Python-sträng, utan som det ast.Module-objekt som kan anslutas till kompilering och eval och exec.

Svar

Jag tycker att det är enklare att kompilera Python-kod som en sträng än vad du föreslår. Men jag vet också att jag bara sa att det inte kommer att övertyga någon, så här är ett exempel.

Vi definierar ett par symboliska huvuden som kommer att representera vårt Python-program i Mathematica och en funktion att återge uttryck med dessa symboliska huvuden:

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 ] 

Vad dessa funktioner tillåter oss är att skriva Python-kod i Mathematica utan att tänka på indraget, det är lite som bygga Python-kod med ast-modulen.

Detta är ett exempel på att göra det symboliska uttrycket som en sträng:

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] 

Ut:

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

Vi kan enkelt bygga vidare på detta och göra vår symboliska representation av Python-programmet mer beskrivande.

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 ] } 

Med dessa hjälpdefinitioner kan vi nu skriva följande program i en mycket läsbar stil.

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] 

Ut:

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) 

Om du inte har det än, snälla slå upp ” symbolisk C i Mathematica-dokumentationen. Det är i grunden ett sätt att bygga en AST för ett C-språkprogram i Mathematica som sedan kan omvandlas till körbar C-kod. Det är i grunden dit vi också är på väg med den här koden, men om jag tänkte göra en fullständig implementering så skulle den inte se exakt så ut (ast-modulen är verkligen värt att studera om man vill gå längs vägen). / p>

Tillbaka till punkten: vad jag vill förmedla med det här svaret är att du inte behöver spendera mycket tid på att bygga ett litet ramverk som mer eller mindre löser det fördjupningsproblem som du nämner i din fråga .

Kommentarer

  • Ja CE detta ser väldigt mycket ut som vad jag föreställde mig skulle vara tortyr att genomföra, och du har rätt, du har gjort det ganska enkelt. Jag måste fortsätta på detta sätt och utvidga språket nedåt mot bladen för att täcka atomer och operatörer och listor och tuplar och sådant, men denna ram hanterar hela fördjupningen, och den borde vara genomförbar.
  • @berniethejet I det här exemplet definierade jag till exempel PyIf till något som utvärderas till något annat. Du kan överväga att istället lägga till definitionen till ToPythonString, dvs. ToPythonString[PyIf[...]] :=. På det sättet kan du inspektera hela programmet i symbolisk form utan att det utvärderas förrän du ringer till ToPythonString.
  • Tack, det är bra att veta. Ditt ramverk är verkligen bättre än något annat jag skulle ha kullat ihop (klagade hela vägen, som jag oundvikligen skulle bli, irriterad över Guido ’ s indrag korståg).

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *