本文最后更新于 2025-10-28,文章内容可能已经过时。

提供两种安全有效的解决方案,用于解析和计算用户输入的数学表达式。

方案一:使用安全的数学表达式解析库(推荐)

最安全可靠的方法是使用专门的数学表达式解析库,如py-expression-eval。这个库专门设计用于安全地解析和计算数学表达式,避免了eval()的安全风险。

安装库

pip install py-expression-eval

代码实现

def calculate_expression():
    expression_str = input("请输入一个数学表达式(例如:2 + 3 * 4):")

    try:
        # 使用py-expression-eval安全解析和计算表达式
        from py_expression_eval import Parser
        parser = Parser()
        result = parser.parse(expression_str).evaluate({})

        print(f"计算结果是:{result}")
    except Exception as e:
        print(f"表达式计算出错:{e}")


if __name__ == "__main__":
    calculate_expression()

方案二:使用运算符优先级实现的解析器(安全替代方案)

若不想安装额外库,可以使用基于栈的运算符优先级解析方法。

def calculate_expression():
    expression_str = input("请输入数学表达式(例如:2+3*4 或 2.5*(3-1)):")

    # 预处理步骤 - 完全忽略空格,正确处理数字和运算符
    tokens = []
    current = ''

    for char in expression_str:
        # 忽略所有空格
        if char == ' ':
            continue

        # 正确处理一元负号(如 -3, 2*-3)
        if char == '-' and (not tokens or tokens[-1] in '(*+-/'):
            current = '-'
            continue

        # 修复1:小数点位置检查(允许在开头或负号后)
        if char == '.':
            # 允许小数点在开头(.5)或负号后(-.5)
            if not current or current[-1] not in '0123456789':
                if current != '-' and not current:  # 允许开头小数点
                    # 保留小数点,但不立即添加
                    pass
                else:
                    # 负号后面可以跟小数点(如 -0.5)
                    pass
            # 如果小数点位置合法,继续加入current
            current += char
            continue

        # 修复2:将小数点从运算符列表中移除
        if char in '()+-*/':
            if current != '':
                tokens.append(current)
                current = ''
            tokens.append(char)
        else:
            current += char

    if current != '':
        tokens.append(current)

    # 验证所有token
    valid_tokens = []
    for token in tokens:
        if token in '()+-*/':
            valid_tokens.append(token)
        else:
            # 检查是否是合法数字(包括负数和小数)
            if token.replace('.', '', 1).replace('-', '', 1).isdigit():
                valid_tokens.append(token)
            else:
                # 修复3:特殊处理小数点开头的情况(如".5")
                if token.startswith('.') and token[1:].replace('-', '', 1).isdigit():
                    valid_tokens.append(token)
                else:
                    raise ValueError(f"无效的表达式元素: '{token}'")

    tokens = valid_tokens

    # 初始化栈
    operand_stack = []
    operator_stack = []

    # 运算符优先级
    def priority(op):
        if op in ('+', '-'): return 1
        if op in ('*', '/'): return 2
        return 0

    # 执行运算
    def apply_op(a, b, op):
        if op == '+': return a + b
        if op == '-': return a - b
        if op == '*': return a * b
        if op == '/':
            if b == 0:
                raise ZeroDivisionError("除数不能为零")
            return a / b
        raise ValueError(f"未知运算符: {op}")

    # 处理表达式
    for token in tokens:
        if token == '(':
            operator_stack.append(token)
        elif token == ')':
            while operator_stack and operator_stack[-1] != '(':
                op = operator_stack.pop()
                b = operand_stack.pop()
                a = operand_stack.pop()
                operand_stack.append(apply_op(a, b, op))
            if not operator_stack or operator_stack[-1] != '(':
                raise ValueError("括号不匹配")
            operator_stack.pop()  # 移除 '('
        elif token in '+-*/':
            while (operator_stack and
                   operator_stack[-1] != '(' and
                   priority(operator_stack[-1]) >= priority(token)):
                op = operator_stack.pop()
                b = operand_stack.pop()
                a = operand_stack.pop()
                operand_stack.append(apply_op(a, b, op))
            operator_stack.append(token)
        else:  # 数字
            operand_stack.append(float(token))

    # 处理剩余运算符
    while operator_stack:
        op = operator_stack.pop()
        if op == '(':
            raise ValueError("括号不匹配")
        b = operand_stack.pop()
        a = operand_stack.pop()
        operand_stack.append(apply_op(a, b, op))

    # 验证结果
    if len(operand_stack) != 1:
        raise ValueError("表达式无效")

    result = operand_stack[0]

    # 检查是否为整数(考虑浮点数精度)
    if abs(result - round(result)) < 1e-5:
        # 是整数,输出整数
        print(f"计算结果是:{int(round(result))}")
    else:
        # 是小数,格式化输出(去掉末尾的0)
        result_str = f"{result:.6f}"
        # 去掉末尾的0
        if '.' in result_str:
            result_str = result_str.rstrip('0').rstrip('.')
        print(f"计算结果是:{result_str}")


if __name__ == "__main__":
    try:
        calculate_expression()
    except Exception as e:
        print(f"错误: {str(e)}")

重要安全提示

强烈建议不要使用eval()函数直接执行用户输入,原因如下:

  1. 安全风险eval()可以执行任意Python代码,用户可能输入恶意代码(如删除文件、访问敏感数据等)

为什么推荐py-expression-eval?

  1. 安全性:专门设计用于安全解析数学表达式,不会执行任意代码
  2. 功能强大:支持多种运算符、常量和函数
  3. 易用性:API设计简洁直观
  4. 专业性:是专门为数学表达式求值设计的库

使用示例

请输入一个数学表达式(例如:2 + 3 * 4):2 + 3 * 4
计算结果是:14.0

如果您需要处理包含变量的表达式(如"x + y"),可以扩展上述代码,让用户输入变量的值:

def calculate_expression_with_variables():
    expression_str = input("请输入一个包含变量的表达式(例如:x + y):")
    x = float(input("请输入x的值:"))
    y = float(input("请输入y的值:"))
    
    try:
        from py_expression_eval import Parser
        parser = Parser()
        result = parser.parse(expression_str).evaluate({'x': x, 'y': y})
        print(f"计算结果是:{result}")
    except Exception as e:
        print(f"表达式计算出错:{e}")

py-expression-eval 是一个用于 Python 的数学表达式求值库,灵感来源于 JavaScript 库 js-expression-eval。它由 Vera Mazhuga 移植及修改以适应 Python 环境。该库提供了一个简洁的 API,允许开发者轻松地解析、评估和简化数学表达式,特别适合于后端验证场景,其中一致性和性能至关重要。许可证为 MIT,确保了广泛的应用可能性。