当我们在使用 pyparsing
模块进行解析时,这就需要我们定义语法规则并编写相应的解析器。以下是一个简单的示例,演示如何使用 pyparsing
解析一个简单的算术表达式并计算其结果,以及我们经常遇到的一些问题解决方案。
1、问题背景
需要能够解析使用 OpenDocument 公式语法的公式,将其解析成 Python 可以理解的语法,但不求解变量值,然后能够多次求解公式,并改变变量的值。
公式可以是用户输入的,因此 pyparsing 允许同时有效地处理公式语法和清理用户输入。有很多 pyparsing 的优秀示例,但所有数学示例似乎都假设立即求解当前范围内的所有内容。
在上下文环境中,我正在研究工业经济模型(生命周期评估或 LCA),其中这些公式表示流程之间的材料或能量交换量。变化量可以是几个参数的函数,例如地理位置。公式和变量引用的链存储在一个有向无环图中,以便公式总是可以简单地求解。公式作为字符串存储在数据库中。
- 问题:
- 是否可以解析公式,以便解析后的求解结果也可以存储在数据库中(作为要评估的字符串或其他内容)?
- 除了目前的方法还有没有其他替代方案?记住,理想的解决方案是一次解析/编写,多次读取。例如,对公式进行部分解析,然后使用 ast 模块,尽管我不知道这如何与数据库存储协同工作。
- 有没有类似项目或库的示例可以参考?我不是程序员,只是一个想在业余时间完成自己的论文并制作一个开源 LCA 软件模型的学生。
- 这种方法是否太慢?我想做大量的蒙特卡罗运行,每次运行可能涉及数万次公式求解(这是一个很大的数据库)。
2、解决方案
回答 1:
-
是的,可以对解析表达式的结果进行序列化,并将其保存到数据库中。然后你只需获取并反序列化表达式,而不是重新解析原始表达式即可。
-
你可以使用内置的 compile 和 eval 来快速完成这项工作,如下面的交互式会话所示:
y = compile("m*x+b","","eval")
m = 100
x = 5
b = 1
eval(y)
501
当然,这具有任何基于 eval 或 exec 的实现的安全性缺陷,因为不受信任或恶意的源字符串可以嵌入有害的系统调用。但如果这是你的论文,并且完全在你自己的控制范围内,不要做任何愚蠢的事情就行了。
- 你可以在 pyparsing wiki 的示例页面找到将表达式解析成"可求解"数据结构的在线示例。特别是,查看 simpleBool.py 和 evalArith.py。如果你想了解更多,可以订购一本 2008年5月号的 Python 杂志,其中有我的文章"使用 Pyparsing 编写一个简单的解释器/编译器",对所使用的方法进行了更详细的描述,以及如何对解析结果进行序列化和反序列化的说明。
- 缓慢的部分是解析,所以你在使用某种中间的可重复求解形式来保存这些结果的道路上是正确的。求解部分应该相当快。第二个缓慢的部分将是从你的数据库中获取这些序列化的结构。在你的蒙特卡罗运行期间,我将封装一个函数,它接受表达式的选择参数,从数据库中获取,并反序列化和返回可求解的表达式。然后,一旦你让它正常工作,使用一个记忆装饰器来缓存这些查询结果对,以便给定表达式只需要获取/反序列化一次。
代码示例:
from pyparsing import Word, nums, alphas, oneOf, ParseException, operatorPrecedence, opAssoc
# 定义语法规则
integer = Word(nums).setParseAction(lambda t: int(t[0]))
variable = Word(alphas, exact=1)
operand = integer | variable
operator = oneOf("+ - * /")
# 定义操作符优先级和结合性
expr = operatorPrecedence(operand,
[
(operator, 2, opAssoc.LEFT),
]
)
# 解析并计算表达式结果
def evaluate_expression(expr_str, vars_dict={}):
vars_dict = vars_dict.copy()
vars_dict.update({}) # 在此添加任何预定义变量
try:
result = expr.parseString(expr_str).evaluate(vars=vars_dict)
return result
except ParseException as e:
return str(e)
# 示例表达式
expression = "x * 5 + 10"
variables = {'x': 2}
# 计算表达式结果
result = evaluate_expression(expression, variables)
print(f"结果: {result}")
这个示例使用 pyparsing
定义了一个简单的语法规则,该规则可以解析包含整数、变量和四则运算符的表达式。然后,它提供了一个函数 evaluate_expression
,该函数接受一个表达式字符串和一个变量字典作为参数,并返回解析结果。
我们可以根据自己的需求和语法规则修改示例代码,以解析和计算更复杂的表达式。
如有更多问题可以留言讨论。