วิธีการประกอบ Python abstract syntax tree (AST) ใน Mathematica
ฉันต้องการที่จะรวบรวม Python ' ast.Module ' วัตถุภายใน Mathematica ExternalEvaluate["exec(astObject)"]แล้วส่งให้พวกเขาผ่านทางงูใหญ่เช่น
ฟังก์ชัน Python ast , parse , eval , execและcompileสามารถทำงานบนอ็อบเจ็กต์ ' ast.Module ' ไม่ว่าจะเป็นเอาต์พุตหรือรับเป็นอินพุต แต่ผมไม่ทราบว่าวิธีการประกอบนี้astObjectภายใน Mathematica / WL ExternalEvaluateแล้วส่งไปที่งูใหญ่ผ่าน
ฉันกำลังพยายามสร้างโค้ด Python ด้วยโปรแกรมใน MMA (สำหรับอัลกอริธึมทางพันธุกรรม) จากนั้นส่งไปยัง Python เพื่อทำการประเมิน ฉันสามารถรวบรวมสตริงโค้ด Python ได้ แต่แล้วฉันก็ต้องจัดการกับการเยื้องทั้งหมดซึ่งดูเหมือนจะเจ็บปวด
ตัวอย่างเช่นใน Python สามารถทำสิ่งต่อไปนี้ได้:
import ast
pythonString="X3=X1*X2"
astObject=ast.parse(pythonString)
X1=3
X2=4
exec(compile(astObject,"","exec"))
print(X3)
-> 12
และแน่นอนจากวีคนี้เป็นไปได้ที่จะทำ:
session=StartExternalSession["Python"]
ExternalEvaluate[session, {"import ast","X1=3","X2=4",
"exec(compile(ast.parse(\"X3=X1*X2\"),\"\",\"exec\"))"} ]
เพื่อให้ได้ผลลัพธ์เดียวกัน (เช่น 12)
แต่ผมต้องการที่จะสร้างบิตของฉันรหัสหลาม ( "X1=3", "X2=4", "X3=X1*X2") ใน Mathematica บิตเหล่านี้ง่ายเพียงพอ แต่ฉันตั้งใจจะสร้างโปรแกรมที่สมบูรณ์เช่นคำสั่งและนิพจน์ metaprogrammatically (!) ในการทำเช่นนั้นฉันต้องหาวิธีแยกวิเคราะห์การเยื้องที่น่ารำคาญของ Python ซึ่งแน่นอนว่ามันแยกความแตกต่างของนิพจน์ชุดหนึ่งจากชุดถัดไปและการอ้างอิงของมันคืออะไร ฉันเกลียดที่จะทำเช่นนั้นและคิดว่ามันอาจจะง่ายกว่าที่จะใช้งานโครงสร้างast
เดิมทีฉันคิดว่าฉันอาจจะสามารถใช้รูปแบบสตริงกลางจากast.dump()ฟังก์ชันของ Python ซึ่งดูเหมือนว่า:
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
ข
PS: นี่คือคำอธิบายของอ็อบเจ็กต์ ' ast.Module ':https://greentreesnakes.readthedocs.io/en/latest/tofrom.html
PPS: ฉันโพสต์ข้ามสิ่งนี้ในชุมชน Wolfram: https://community.wolfram.com/groups/-/m/t/2070851
คำตอบ
ฉันไม่แน่ใจว่าคุณกำลังมองหาอะไรกันแน่ ฉันคิดว่าคำตอบของคำถามของคุณ:
ฉันกำลังพยายามสร้างโค้ด Python โดยใช้โปรแกรมใน MMA [... ] จากนั้นส่งไปยัง Python เพื่อทำการประเมิน ฉันสามารถรวบรวมสตริงโค้ด Python ได้ แต่แล้วฉันก็ต้องจัดการกับการเยื้องทั้งหมดซึ่งดูเหมือนจะเจ็บปวด
เป็นไปได้ไหมที่จะสร้างวัตถุ Python ประเภทนี้ทางด้าน Mathematica
ค่อนข้างตรงไปตรงมาโดยใช้ExternalEvaluateไลบรารี "ast" ของ Python
นี่คือตัวอย่าง:
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} *)
(ฉันใช้evalแทนexecเพราะevalส่งคืนค่า)
ฉันคิดว่าการรวบรวมโค้ด Python เป็นสตริงนั้นง่ายกว่าที่คุณเสนอ แต่ฉันก็รู้ด้วยว่าฉันแค่พูดมันจะไม่ทำให้ใครมั่นใจได้ดังนั้นนี่คือตัวอย่าง
เรากำหนดสัญลักษณ์สองหัวที่จะแทนโปรแกรม Python ของเราใน Mathematica และฟังก์ชันในการแสดงผลนิพจน์ด้วยหัวสัญลักษณ์เหล่านั้น:
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
]
สิ่งที่ฟังก์ชันเหล่านี้ช่วยให้เราทำได้คือการเขียนโค้ด Python ใน Mathematica โดยไม่ต้องคิดถึงการเยื้องมันเหมือนกับการสร้างโค้ด Python ด้วยโมดูล ast
นี่คือตัวอย่างของการแสดงผลนิพจน์สัญลักษณ์เป็นสตริง:
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]
ออก:
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]
ออก:
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 โดยพื้นฐานแล้วเป็นวิธีการสร้าง AST สำหรับโปรแกรมภาษา C ใน Mathematica ซึ่งสามารถแปลงเป็นรหัส C ที่รันได้ นั่นเป็นจุดที่เรามุ่งหน้าไปพร้อมกับโค้ดนี้เช่นกันแม้ว่าฉันตั้งใจจะทำให้การใช้งานสมบูรณ์แบบนั้นมันจะไม่เป็นแบบนี้ (โมดูล ast นั้นคุ้มค่าที่จะศึกษาอย่างแน่นอนหากใครต้องการไปตามเส้นทาง)
กลับไปที่ประเด็น: สิ่งที่ฉันต้องการจะสื่อด้วยคำตอบนี้ก็คือคุณไม่จำเป็นต้องใช้เวลามากในการสร้างกรอบงานเล็ก ๆ ที่ช่วยแก้ปัญหาการเยื้องที่คุณพูดถึงในคำถามของคุณ