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

Eu gostaria de montar Python “ ast.Module “objetos dentro do Mathematica e, em seguida, envie-os para Python via, por exemplo ExternalEvaluate["exec(astObject)"].

O Python ast , parse , eval , exec e as funções compilar podem operar em “ ast.Module “objetos, emitindo-os ou tomando-os como entradas. No entanto, não sei como montar isso astObject dentro do Mathematica / WL e, em seguida, enviá-lo para Python via ExternalEvaluate.

Estou tentando gerar código Python programaticamente em MMA (para um algoritmo genético) e enviá-lo para Python para avaliação. Eu poderia montar strings de código Python, mas depois 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, do MMA é possível fazer:

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

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

No entanto, desejo gerar meus bits 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, tenho que 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. Não gosto de 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 função Python “s ast.dump() 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 desde astString essencialmente serializa o astObject Eu também poderia gerar isso. No entanto, não consigo encontrar nenhuma maneira de fazer com que o Python faça algo com isso astString.

É possível criar esse tipo de objeto Python – como meu astObject acima – no lado do Mathematica?

B

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

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

Comentários

  • ” Para fazer isso, preciso descobrir como analisar Python ‘ s indentações irritantes […] ” – Eu sei que você está pedindo para usar AST, mas talvez as respostas que lidam com as ‘ recortes do Python também sejam do seu interesse.
  • Sim, Anton, você está certo, também estou procurando soluções puramente baseadas em strings. E ao longo dessas linhas, eu estava olhando para o seu pacote FunctionalParsers.m como uma forma de importar automaticamente Python ‘ gramática EBNF para talvez me permitir gerar pseudo-Python com colchetes explícitos , ou talvez para gerar código Hy (que é Lisp escrito em Python) que, claro, tem colchetes apropriados. Eu simplesmente não consigo ‘ não entender por que alguém criaria uma linguagem com base na indentação ou por que outros ‘ s iriam então usá-la …
  • ” Eu simplesmente não consigo ‘ não entender por que alguém criaria um idioma baseado em indentação, ou por que outros ‘ s iriam então usá-lo … ” – LOL! Muitas pessoas ficam perplexas com isso!
  • Usar ” FunctionalParsers.m ” é uma ideia interessante, mas persegui-la pode estar longe de ser uma tarefa fácil …
  • Seria melhor programar duas mônadas de software, uma em WL e outra em Python, que têm o mesmo design de fluxo de trabalho. Então você transfere entre eles com pequenas alterações de código; também nenhuma formatação especial para Python é necessária. Aqui está um exemplo com uma mônada chamada LSAMon . (Role até o final.)

Resposta

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

Estou tentando gerar código Python programaticamente no MMA […] e, em seguida, envie-o para Python para avaliação. Eu poderia montar strings de código Python, mas depois 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 usando ExternalEvaluate e Python “s ” ast ” biblioteca.

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

(Eu usei eval em vez de exec, porque eval retorna um valor.)

Comentários

  • Obrigado Anton. Sim, eu poderia ter sido mais claro. Deixe-me atualizar um pouco a minha pergunta. Basicamente, quero gerar o ” ‘ [i ** 2 para i no intervalo (10)] ‘ ” participe de sua resposta no lado do MMA, mas não como uma string Python, mas como o objeto ast.Module que pode ser conectado ao compile e eval e exec.

Resposta

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 apenas dizendo que não vai convencer ninguém, 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 sobre a indentação, é um pouco como construção de 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] 

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

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 seguinte programa 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] 

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) 

Se você ainda não fez isso, 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).

De volta ao ponto: o que eu 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 recuo que você mencionou em sua pergunta .

Comentários

  • Sim CE, isso se parece muito com o que eu estava imaginando que seria uma tortura de implementar, e você está certo, você conseguiu bastante simples. Terei que continuar desta forma estendendo a linguagem para baixo em direção às folhas para cobrir átomos e operadores e listas e tuplas e tal, mas esta estrutura lida com todo o recuo e deve ser factível.
  • @berniethejet Neste exemplo, eu defini por exemplo PyIf para algo que é avaliado em outra coisa. Em vez disso, você pode adicionar a definição a ToPythonString, ou seja, ToPythonString[PyIf[...]] :=. Dessa forma, você pode inspecionar todo o seu programa de forma simbólica, sem que seja avaliado até que você chame ToPythonString.
  • Obrigado, é bom saber. Sua estrutura é certamente melhor do que qualquer outra que eu teria remendado (resmungando o tempo todo, como seria inevitável, irritado com a cruzada de recuo de Guido ‘).

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *