MathematicaでPython抽象構文木(AST)を組み立てる方法は?

Python “ ast.Module “オブジェクトを次に、例えば経由してPythonに送信します。 ExternalEvaluate["exec(astObject)"]

Python ast parse eval exec および compile 関数はすべて “ ast.Module “オブジェクト。出力するか、入力として受け取ります。ただし、この astObject をMathematica / WL内でアセンブルする方法がわかりません。次に、ExternalEvaluateを介してPythonに送信します。

MMAでPythonコードをプログラムで生成し(遺伝子アルゴリズム用)、Pythonに送信しようとしています。評価のために。Pythonコード文字列をアセンブルすることはできますが、すべてのインデントを処理する必要があり、これは面倒なようです。

たとえば、Pythonでは次のことが可能です。

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

そしてもちろんMMAから次のことが可能です:

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

同じものを生成する結果(つまり12)。

ただし、Pythonコードのビットを生成したい("X1=3""X2=4""X3=X1*X2")Mathematicaで。これらのビットは十分に単純ですが、完全なプログラム、つまりステートメントと式をメタプログラムで生成するつもりです(!)。そのためには、次のことを行う必要があります。 Pythonの厄介なインデントを解析する方法を理解します。これはもちろん、式のセットを次のセットと区別する方法と、それらの依存関係を理解するためです。私はそうするのが嫌いで、 ast 構造を操作する方が簡単かもしれないと考えました。

元々、中間の文字列形式を使用できるかもしれないと思っていました。 Pythonのast.dump()関数から次のようになります。

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

これ以降astStringは基本的にastObjectをシリアル化します。代わりにこれを生成することもできますが、PythonにこのastString

この種のPythonオブジェクト(上記のastObjectのような)をMathematica側で作成することは可能ですか?

B

PS:「 ast.Module 」オブジェクトの説明は次のとおりです: https://greentreesnakes.readthedocs.io/en/latest/tofrom.html

PPS:これをWolframコミュニティにクロスポストしました: https://community.wolfram.com/groups/-/m/t/2070851

コメント

  • “そのためには、Python ‘の迷惑なインデントを解析する方法を理解する必要があります[…] ” -ASTの使用を求めていることは承知していますが、Python ‘のインデントを扱う回答も興味深いでしょう。
  • はい、アントン、その通りです。私は純粋に文字列ベースのソリューションも検討しています。そして、それらの線に沿って、Python ‘のEBNF文法を自動的にインポートして、疑似Python-with-explicit-bracketingを生成できるようにする方法としてFunctionalParsers.mパッケージを見ていました。 、またはおそらく適切なブラケットを持つHyコード(Pythonで書かれたLisp)を生成するため。 ‘誰かがインデントに基づいて言語を作成する理由、または他の’がそれを使用し続ける理由を理解できません。 …
  • “インデントに基づいて誰かが言語を作成する理由が理解できない’他の’がそれを使用し続ける理由… ” -LOL!多くの人がそれを不思議に思っています!
  • ” FunctionalParsers.m “を使用するのは興味深いアイデアですが、それを追求する簡単な作業とはほど遠いかもしれません…
  • 同じワークフロー設計を持つ2つのソフトウェアモナド(1つはWLで、もう1つはPythonで)をプログラミングする方がよい場合があります。次に、コードを少し変更してそれらの間を転送します。また、Python用の特別なフォーマットは必要ありません。 これはLSAMon というモナドの例です。 (一番下までスクロールします。)

回答

正確に何を探しているのかわかりません。あなたの質問の答えだと思います:

MMAでPythonコードをプログラムで生成しようとしています[…]次に、評価のためにPythonに送信します。 Pythonコード文字列をアセンブルすることはできますが、すべてのインデントを処理する必要があり、これは面倒なようです。

この種のPythonオブジェクトをMathematica側で作成することは可能ですか?

ExternalEvaluateとPythonの” ast

ライブラリ。

例を次に示します。

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

(iv id =を使用しましたevalが値を返すため、execの代わりに “30ec86f1e9”>

。)

コメント

  • アントンに感謝します。はい、もっと明確にできたはずです。質問を少し更新します。基本的に、” ‘ [i ** 2 for i in range(10)] ‘ ” MMA側で回答に参加しますが、Python文字列としてではなく、compile、eval、execにプラグインできるast.Moduleオブジェクトとして使用します。

回答

Pythonコードを文字列としてコンパイルすることは、実際には提案するよりも簡単だと思います。しかし、「だれにも納得できない」と言っているだけでも、例を示します。

MathematicaでPythonプログラムを表すいくつかのシンボリックヘッドと、レンダリングする関数を定義します。これらのシンボリックヘッドを使用した式:

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 ] 

これらの関数を使用すると、インデントを考慮せずにMathematicaでPythonコードを記述できます。 astモジュールを使用してPythonコードを作成します。

これは、シンボリック式を文字列としてレンダリングする例です。

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

これを簡単に構築して、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 ] } 

これらのヘルパー定義を使用して、次のプログラムを非常に読みやすいスタイルで記述できるようになりました。

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) 

まだの場合は、”シンボリックC 。これは基本的にMathematicaでC言語プログラム用のASTを構築する方法であり、それを実行可能なCコードに変換することができます。それは基本的に私たちがこのコードに向かっているところですが、私がそのような完全な実装をするつもりなら、それは正確にはこのようには見えません(astモジュールは確かにルートを下りたいのであれば勉強する価値があります)

要点に戻る:この回答で伝えたいのは、質問で言及したインデントの問題を多かれ少なかれ解決する小さなフレームワークを構築するために多くの時間を費やす必要がないということです。 。

コメント

  • はいCEこれは、私が想像していた実装の拷問に非常によく似ています。あなたは正しいです、あなたはそれを成し遂げました非常に簡単です。アトム、演算子、リスト、タプルなどをカバーするために、この方法で言語を葉に向かって下向きに拡張し続ける必要がありますが、このフレームワークはすべてのインデントを処理し、実行可能である必要があります。
  • @berniethejetこの例では、たとえばPyIfを他の何かに評価されるものに定義しました。代わりに、定義をToPythonString、つまりToPythonString[PyIf[...]] :=に追加することを検討してください。そうすれば、ToPythonStringを呼び出すまでプログラムを評価せずに、プログラム全体を記号形式で検査できます。
  • ありがとうございます。あなたのフレームワークは確かに私が一緒に石畳にしたものよりも優れています(私が必然的にそうなるように、Guido ‘のインデント十字軍に悩まされています)。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です