Como montar árvores de sintaxe abstrata Python (AST) no Mathematica?

Sep 10 2020

Gostaria de montar objetos Python ' ast.Module ' dentro do Mathematica e, em seguida, enviá-los para Python via, por exemplo ExternalEvaluate["exec(astObject)"].

As funções ast , parse , eval , exec e compile do Python podem operar em objetos ' ast.Module ', emitindo- os ou tomando-os como entradas. Porém, não sei como montar isso astObjectdentro do Mathematica / WL e depois enviar para o Python via ExternalEvaluate.

Estou tentando gerar código Python programaticamente em MMA (para um algoritmo genético) e, em seguida, enviá-lo ao Python para avaliação. Eu poderia montar strings de código Python, mas então tenho que lidar com todo o recuo, o que parece uma dor.

Por exemplo, em Python, é possível fazer o seguinte:

import ast

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

-> 12

E é claro que no MMA é possível fazer:

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

para produzir o mesmo resultado (ou seja, 12).

No entanto eu quero gerar meus pedaços de código Python ( "X1=3", "X2=4", "X3=X1*X2") no Mathematica. Esses bits aqui são bastante simples, mas pretendo gerar programas completos, ou seja, declarações e expressões, metaprogrammaticamente (!). Para fazer isso, preciso descobrir como analisar as indentações irritantes do Python, que é, obviamente, como ele distingue um conjunto de expressões do próximo e quais são suas dependências. Eu detesto fazer isso e imaginei que seria mais fácil operar na estrutura ast .

Originalmente, pensei que poderia usar uma forma de string intermediária da ast.dump()função do Python que se parece com:

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 como isso astStringessencialmente serializa o, astObjecteu também poderia gerar isso. No entanto, não consigo encontrar nenhuma maneira de fazer o Python fazer nada com isso astString.

É possível criar esse tipo de objeto Python - como o meu astObjectacima - no lado do Mathematica?

B

PS: Aqui está uma descrição dos objetos ' ast.Module ':https://greentreesnakes.readthedocs.io/en/latest/tofrom.html

PPS: Eu postei isso na comunidade Wolfram: https://community.wolfram.com/groups/-/m/t/2070851

Respostas

7 AntonAntonov Sep 10 2020 at 03:34

Não tenho certeza do que exatamente você está procurando. Acho que a resposta da sua pergunta:

Estou tentando gerar código Python programaticamente em MMA e [...] enviá-lo ao Python para avaliação. Eu poderia montar strings de código Python, mas então tenho que lidar com todo o recuo, o que parece uma dor.

É possível criar esse tipo de objeto Python no lado do Mathematica?

é bastante simples de usar uma ExternalEvaluatebiblioteca "ast" do Python.

Aqui está um exemplo:

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

(Usei em evalvez de exec, porque evalretorna um valor.)

2 C.E. Sep 11 2020 at 00:36

Acho que compilar o código Python como uma string é realmente mais simples do que o que você propõe. Mas também sei que só de dizer isso não vai convencer ninguém, então aqui está um exemplo.

Definimos algumas cabeças simbólicas que representarão nosso programa Python no Mathematica e uma função para renderizar expressões com essas cabeças 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
  ]

O que essas funções nos permitem fazer é escrever código Python no Mathematica sem pensar na indentação, é um pouco como construir código Python com o módulo ast.

Este é um exemplo de renderização da expressão simbólica como uma string:

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]

Fora:

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 facilmente desenvolver isso e tornar nossa representação simbólica do programa Python mais descritiva.

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

Com essas definições auxiliares, agora podemos escrever o programa a seguir em um estilo muito legível.

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]

Fora:

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 ainda não o fez, procure "C simbólico" na documentação do Mathematica. É basicamente uma maneira de construir um AST para um programa de linguagem C no Mathematica que pode então ser convertido em código C executável. Isso é basicamente para onde estamos indo com este código também, embora se eu pretendesse fazer uma implementação completa como essa não seria exatamente assim (o módulo ast certamente vale a pena estudar se alguém quiser seguir o caminho).

Voltando ao ponto: o que quero transmitir com esta resposta é que você não precisa gastar muito tempo para construir uma pequena estrutura que mais ou menos resolva o problema de indentação que você mencionou em sua pergunta.