首页 > 解决方案 > 如何在不使用 eval() 的情况下将字符串的用户输入评估为数学表达式?

问题描述

我需要制作一个程序来接受数学表达式的用户输入(expr)并处理找到答案。但是,我们不允许使用 eval() 函数。赋值声明我们应该假设用户将输入的唯一运算符是:+、-、*、/、%。操作数也被假定为一位整数。

我的想法是将操作数转换为整数并列出所有可以使用的运算符。然后,使用 if 语句查看运算符如何匹配我的列表。

到目前为止,我能够想出这些代码行:

operand1 = expr[1]
operator = expr[2]
operand2 = expr[3]

operators = ['+','-','*','/','%']

我这样做是为了索引输入表达式中每个操作数和运算符的位置。我被困在这里,希望有人能给我一些关于如何前进的帮助。代码的结果需要输出用户输入的表达式,以及表达式的结果。如果表达式的第二个操作数是 0 并且运算符是除法,则代码输出“无”。

标签: pythonpython-3.xstringtype-conversion

解决方案


您可以使用 解析表达式ast.parse

>>> import ast
>>> expr = ast.parse("3 + 5", mode="eval")

然后您可以分析生成的解析树。在这种特殊情况下,您关心表达式的主体。

>>> expr
<_ast.Expression object at 0x10992c438>
>>> expr.body
<_ast.BinOp object at 0x10a926320>

该对象具有感兴趣的属性:leftopright。你会看运营商

>>> expr.body.op
<_ast.Add object at 0x10a91a208>

决定如何处理操作数。

>>> expr.body.left.n + expr.body.right.n
8

所以一个可以处理乘法和加法的简单递归函数可能看起来像

def evaluate_expr(expr):
    if isinstance(expr, ast.Expression):
        return evaluate_expr(expr.body)
    elif isinstance(expr, ast.Num):
        return expr.n
    elif isinstance(expr, ast.BinOp):
        op = expr.op
        left = evaluate_expr(expr.left)
        right = evaluate_expr(expr.right)
        if isinstance(op, ast.Add):
            return left + right
        elif isinstance(op, ast.Mult):
            return left * right
    raise ValueError(f"Can't evaluate {expr}")

e = ast.parse("3 + 5 * 2", mode="eval")
print(evaluate_expr(e.body))  # Outputs 13

请参阅ast模块的文档以了解树中可以出现哪些其他节点,以便您可以适应evaluate_expr处理其他操作,括号等ast.dump也有助于探索表达式是如何解析的。

>>> ast.dump(e, annotate_fields=False)
'Expression(BinOp(Num(3), Add(), BinOp(Num(5), Mult(), Num(2))))'

这清楚地表明解析器处理优先级:3 + 5 * 2不是3 + 5将结果乘以 2,而是3加上5 * 2(较低的节点具有较高的优先级,因为树是自下而上评估的)的结果。


这假定您的输入实际上是一个有效的 Python 表达式。如果没有,您将需要编写自己的解析器,但是一旦有了解析树,该树的评估就会以类似的方式进行(尽管树的节点是您在解析中创建的任何内容,不一定是ast.Binet al . 由 ) 创建的节点ast.parse


推荐阅读