Jak zebrać abstrakcyjne drzewa składni Pythona (AST) w Mathematica?

Chciałbym złożyć Python „ ast.Module „wewnątrz Mathematica, a następnie prześlij je do Pythona przez np ExternalEvaluate["exec(astObject)"].

Python ast , przeanalizuj , eval , exec i kompiluj funkcje mogą działać na „ ast.Module „, wyprowadzając je lub przyjmując jako dane wejściowe. Jednak nie wiem, jak złożyć to astObject w Mathematica / WL a następnie wyślij go do Pythona przez ExternalEvaluate.

Próbuję programowo wygenerować kod Pythona w MMA (dla algorytmu genetycznego), a następnie przesłać go do Pythona do oceny. Mógłbym złożyć ciągi kodu w Pythonie, ale potem muszę obsłużyć wszystkie wcięcia, co wydaje się uciążliwe.

Na przykład w Pythonie można wykonać następujące czynności:

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

I oczywiście z MMA można to zrobić:

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

, aby uzyskać to samo wynik (tj. 12).

Jednak chcę wygenerować moje bity kodu Pythona ("X1=3", "X2=4", "X3=X1*X2") w Mathematica. Te bity tutaj są dość proste, ale zamierzam generować kompletne programy, tj. instrukcje i wyrażenia, metaprogramowo (!). Aby to zrobić, muszę wtedy dowiedzieć się, jak analizować irytujące wcięcia Pythona, czyli oczywiście sposób, w jaki odróżnia on jeden zestaw wyrażeń od drugiego i jakie są ich zależności. Nie chcę tego robić i doszedłem do wniosku, że może być łatwiej operować na strukturze ast .

Początkowo myślałem, że będę mógł użyć pośredniej formy ciągu z funkcji Pythona „s ast.dump(), która wygląda następująco:

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

i od tego astString zasadniczo serializuje astObject Zamiast tego mógłbym również wygenerować ten plik. Jednak nie mogę znaleźć żadnego sposobu, aby zmusić Pythona do zrobienia czegokolwiek z tym astString.

Czy jest możliwe utworzenie tego rodzaju obiektu Pythona – takiego jak mój astObject powyżej – po stronie Mathematica?

B

PS: Oto opis obiektów „ ast.Module „: https://greentreesnakes.readthedocs.io/en/latest/tofrom.html

PPS: Opublikowałem to w społeczności Wolfram: https://community.wolfram.com/groups/-/m/t/2070851

Komentarze

  • ” Aby to zrobić, muszę następnie dowiedzieć się, jak przeanalizować Pythona ' irytujące wcięcia […] ” – Wiem, że prosisz o użycie AST, ale być może interesują Cię również odpowiedzi dotyczące wcięć Pythona '.
  • Tak, Anton, masz rację. Patrzę też na rozwiązania oparte na strunach. W związku z tym patrzyłem na twój pakiet FunctionalParsers.m jako sposób na automatyczne importowanie gramatyki EBNF ' Pythona, aby być może pozwolił mi wygenerować pseudo-Python-z-jawnym-nawiasami lub być może do wygenerowania kodu Hy (który jest napisany w Lispie w Pythonie), który oczywiście ma odpowiednie nawiasy. Po prostu mogę ' nie rozumieć, dlaczego ktoś miałby tworzyć język na podstawie wcięć lub dlaczego inne ' osoby miałyby go używać …
  • ” Po prostu mogę ' nie rozumieć, dlaczego ktoś miałby tworzyć język na podstawie wcięć lub dlaczego inne ' s następnie używałyby go … ” – LOL! Wiele osób jest tym zdziwionych!
  • Używanie ” FunctionalParsers.m ” to ciekawy pomysł, ale w dalszym ciągu go realizujemy może być dalekie od łatwego przedsięwzięcia …
  • Lepiej byłoby zaprogramować dwie monady programowe, jedną w WL, a drugą w Pythonie, które mają ten sam projekt przepływu pracy. Następnie przenosisz się między nimi z niewielkimi zmianami w kodzie; również nie jest potrzebne żadne specjalne formatowanie dla Pythona. Oto przykład z monadą o nazwie LSAMon . (Przewiń do dołu.)

Odpowiedź

Nie jestem pewien, czego dokładnie szukasz. Myślę, że odpowiedź na Twoje pytanie:

Próbuję programowo wygenerować kod Pythona w MMA […], a następnie prześlij go do Pythona w celu oceny. Mógłbym złożyć ciągi kodu Pythona, ale potem muszę obsłużyć wszystkie wcięcia, co wydaje się uciążliwe.

Czy jest możliwe utworzenie tego rodzaju obiektu Pythona po stronie Mathematica?

jest całkiem proste dzięki ExternalEvaluate i Python „s ” ast ” biblioteka.

Oto przykład:

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

(użyłem eval zamiast exec, ponieważ eval zwraca wartość.)

Komentarze

  • Dziękuję Anton. Tak, mogłem wyrazić się bardziej jasno. Pozwól, że zaktualizuję trochę moje pytanie. Zasadniczo chcę wygenerować ” ' [i ** 2 for i in range (10)] ' ” udział w odpowiedzi po stronie MMA, ale nie jako napis w Pythonie, ale jako obiekt ast.Module, który można podłączyć do compile, eval i exec.

Odpowiedź

Myślę, że skompilowanie kodu Pythona jako ciągu znaków jest w rzeczywistości prostsze niż to, co proponujesz. Ale wiem też, że samo powiedzenie tego nikogo nie przekona, więc oto przykład.

Definiujemy kilka symbolicznych głów, które będą reprezentować nasz program w języku Python w Mathematica, oraz funkcję do renderowania wyrażenia z tymi symbolicznymi główkami:

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 ] 

Te funkcje pozwalają nam pisać kod Pythona w Mathematica bez zastanawiania się nad wcięciami, to trochę tak, jak budowanie kodu Pythona za pomocą modułu ast.

Oto przykład renderowania wyrażenia symbolicznego jako ciągu:

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

Możemy z łatwością na tym zbudować i uczynić naszą symboliczną reprezentację programu w Pythonie bardziej opisową.

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

Dzięki tym definicjom pomocników możemy teraz napisać następujący program w bardzo czytelnym stylu.

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] 

Out:

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) 

Jeśli jeszcze tego nie zrobiłeś, sprawdź ” symboliczne C w dokumentacji Mathematica. Jest to po prostu sposób na zbudowanie AST dla programu w języku C w Mathematica, który można następnie przekonwertować na działający kod C. W zasadzie do tego właśnie zmierzamy z tym kodem, chociaż gdybym miał zamiar wykonać pełną implementację w ten sposób, nie wyglądałby dokładnie tak (moduł ast jest z pewnością wart studiowania, jeśli chce się pójść tą drogą).

Wracając do tematu: tym, co chcę przekazać tą odpowiedzią, jest to, że nie musisz poświęcać dużo czasu na zbudowanie małego frameworka, który mniej więcej rozwiązuje problem z wcięciami, o którym wspominasz w swoim pytaniu .

Komentarze

  • Tak, CE, to wygląda bardzo podobnie do tego, co wyobrażałem sobie jako tortury do wprowadzenia, i masz rację, udało ci się to całkiem proste. Będę musiał kontynuować w ten sposób, rozszerzając język w dół w kierunku liści, aby objąć atomy i operatory, listy, krotki i tym podobne, ale ta struktura obsługuje wszystkie wcięcia i powinno być wykonalne.
  • @berniethejet W tym przykładzie zdefiniowałem na przykład PyIf do czegoś, co zmienia się w coś innego. Możesz zamiast tego dodać definicję do ToPythonString, tj. ToPythonString[PyIf[...]] :=. W ten sposób możesz sprawdzić cały program w formie symbolicznej, bez konieczności oceniania go do momentu wywołania ToPythonString.
  • Dziękuję, dobrze wiedzieć. Twoja struktura jest z pewnością lepsza niż wszystkie, które bym ułożył razem (narzekając przez całą drogę, jak nieuchronnie byłbym zirytowany krucjatą Guido ').

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *