在Python中解析包含子公式的方程式
我正在尝试用编译器的方法在Python中开发一个方程解析器。遇到的主要问题是,我可能没有所有的变量,因此需要寻找子公式。下面的例子说明得很清楚;)
我有四个已知值的变量:vx、vy、vz和c:
list_know_var = ['vx', 'vy', 'vz', 'c']
我想计算马赫数(M),它的定义是
equation = 'M = V / c'
我已经知道变量c,但不知道V。不过,我知道速度V可以通过vx、vy和vz计算出来,这个计算结果存储在一个字典里,里面还有其他公式(这里只展示了一个子公式)
know_equations = {'V': '(vx ** 2 + vy ** 2 + vz ** 2) ** 0.5'}
因此,我需要做的是解析这个方程,检查我是否有所有的变量。如果没有,我就要查看know_equations字典,看看是否有定义的方程,并且这个过程会递归进行,直到我的方程完全定义好。
目前为止,我已经能够使用这里提供的答案来解析我的方程,并判断我是否知道所有的变量。问题是,我还没有找到方法来用know_equation中的表达式替换未知变量(这里是V):
parsed_equation = compiler.parse(equation)
list_var = re.findall("Name\(\'(\w*)\'\)", str(parsed_equation.getChildNodes()[0]))
list_unknow_var = list(set(list_var) - set(list_know_var))
for var in list_unknow_var:
if var in know_equations:
# replace var in equation by its expression given in know_equations
# and repeate until all the variables are known or raise Error
pass
提前感谢你的帮助,非常感激!
Adrien
1 个回答
1
我来随便说说这个问题。
compiler.parse
这个函数会返回一个叫做compiler.ast.Module
的东西,它里面包含了一个抽象语法树。你可以用getChildNodes
这个方法来遍历这个树。通过递归地检查每个节点的left
和right
属性,你可以找到compiler.ast.Name
的实例,并把它们替换成你想要的表达式。
举个例子:
import compiler
def recursive_parse(node,substitutions):
# look for left hand side of equation and test
# if it is a variable name
if hasattr(node.left,"name"):
if node.left.name in substitutions.keys():
node.left = substitutions[node.left.name]
else:
# if not, go deeper
recursive_parse(node.left,substitutions)
# look for right hand side of equation and test
# if it is a variable name
if hasattr(node.right,"name"):
if node.right.name in substitutions.keys():
node.right = substitutions[node.right.name]
else:
# if not, go deeper
recursive_parse(node.right,substitutions)
def main(input):
substitutions = {
"r":"sqrt(x**2+y**2)"
}
# each of the substitutions needs to be compiled/parsed
for key,value in substitutions.items():
# this is a quick ugly way of getting the data of interest
# really this should be done in a programatically cleaner manner
substitutions[key] = compiler.parse(substitutions[key]).getChildNodes()[0].getChildNodes()[0].getChildNodes()[0]
# compile the input expression.
expression = compiler.parse(input)
print "Input: ",expression
# traverse the selected input, here we only pass the branch of interest.
# again, as with above, this done quick and dirty.
recursive_parse(expression.getChildNodes()[0].getChildNodes()[0].getChildNodes()[1],substitutions)
print "Substituted: ",expression
if __name__ == "__main__":
input = "t = r*p"
main(input)
我承认我只在几个例子上测试过这个,但我觉得这个方法的基础是可以的,可以处理很多不同的输入。
运行这个代码后,我得到了以下输出:
Input: Module(None, Stmt([Assign([AssName('t', 'OP_ASSIGN')], Mul((Name('r'), Name('p'))))]))
Substituted: Module(None, Stmt([Assign([AssName('t', 'OP_ASSIGN')], Mul((CallFunc(Name('sqrt'), [Add((Power((Name('x'), Const(2))), Power((Name('y'), Const(2)))))], None, None), Name('p'))))]))
编辑:
在Python 3.0中,compiler模块已经不再推荐使用,所以更好(也更干净)的解决方案是使用ast
模块:
import ast
from math import sqrt
# same a previous recursion function but with looking for 'id' not 'name' attribute
def recursive_parse(node,substitutions):
if hasattr(node.left,"id"):
if node.left.id in substitutions.keys():
node.left = substitutions[node.left.id]
else:
recursive_parse(node.left,substitutions)
if hasattr(node.right,"id"):
if node.right.id in substitutions.keys():
node.right = substitutions[node.right.id]
else:
recursive_parse(node.right,substitutions)
def main(input):
substitutions = {
"r":"sqrt(x**2+y**2)"
}
for key,value in substitutions.items():
substitutions[key] = ast.parse(substitutions[key], mode='eval').body
# As this is an assignment operation, mode must be set to exec
module = ast.parse(input, mode='exec')
print "Input: ",ast.dump(module)
recursive_parse(module.body[0].value,substitutions)
print "Substituted: ",ast.dump(module)
# give some values for the equation
x = 3
y = 2
p = 1
code = compile(module,filename='<string>',mode='exec')
exec(code)
print input
print "t =",t
if __name__ == "__main__":
input = "t = r*p"
main(input)
这个方法会编译表达式并在本地环境中执行。输出应该是:
Input: Module(body=[Assign(targets=[Name(id='t', ctx=Store())], value=BinOp(left=Name(id='r', ctx=Load()), op=Mult(), right=Name(id='p', ctx=Load())))])
Substituted: Module(body=[Assign(targets=[Name(id='t', ctx=Store())], value=BinOp(left=Call(func=Name(id='sqrt', ctx=Load()), args=[BinOp(left=BinOp(left=Name(id='x', ctx=Load()), op=Pow(), right=Num(n=2)), op=Add(), right=BinOp(left=Name(id='y', ctx=Load()), op=Pow(), right=Num(n=2)))], keywords=[], starargs=None, kwargs=None), op=Mult(), right=Name(id='p', ctx=Load())))])
t = r*p
t = 3.60555127546