安全地评估简单的字符串方程

新手上路,请多包涵

我正在编写一个程序,其中将方程式作为字符串输入,然后进行计算。到目前为止,我想出了这个:

 test_24_string = str(input("Enter your answer: "))
test_24 = eval(test_24_string)

我需要这个等式的字符串版本和评估版本。然而, eval 是一个非常危险的功能。但是,使用 int() 不起作用,因为它是一个等式。是否有一个 Python 函数可以从字符串中计算数学表达式,就像输入数字一样?

原文由 Aamri 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 502
2 个回答

一种方法是使用 numexpr 。它主要是一个用于优化(和多线程) numpy 操作的模块,但它也可以处理数学 python 表达式:

 >>> import numexpr
>>> numexpr.evaluate('2 + 4.1 * 3')
array(14.299999999999999)

您可以在结果上调用 .item 以获得类似 python 的类型:

 >>> numexpr.evaluate('17 / 3').item()
5.666666666666667

这是一个第三方扩展模块,所以它在这里可能完全矫枉过正,但它绝对比 eval 更安全,并且支持相当多的功能(包括 numpymath 操作).如果还支持“变量替换”:

 >>> b = 10
>>> numexpr.evaluate('exp(17) / b').item()
2415495.27535753


使用 python 标准库的一种方法,尽管非常有限,是 ast.literal_eval 。它适用于 Python 中最基本的数据类型和文字:

 >>> import ast
>>> ast.literal_eval('1+2')
3

但是失败了更复杂的表达式,例如:

 >>> ast.literal_eval('import os')
SyntaxError: invalid syntax

>>> ast.literal_eval('exec(1+2)')
ValueError: malformed node or string: <_ast.Call object at 0x0000023BDEADB400>

不幸的是,除了 +- 之外的任何运营商都是不可能的:

 >>> ast.literal_eval('1.2 * 2.3')
ValueError: malformed node or string: <_ast.BinOp object at 0x0000023BDEF24B70>

我在这里复制了部分文档,其中包含受支持的类型:

安全地评估包含 Python 文字或容器显示的表达式节点或字符串。提供的字符串或节点只能由以下 Python 文字结构组成:字符串、字节、数字、元组、列表、字典、集合、布尔值和 None。

原文由 MSeifert 发布,翻译遵循 CC BY-SA 3.0 许可协议

编写后缀表达式求值器并不难。下面是一个工作示例。 (也 可以 在 github 上找到。)

 import operator
import math

_add, _sub, _mul = operator.add, operator.sub, operator.mul
_truediv, _pow, _sqrt = operator.truediv, operator.pow, math.sqrt
_sin, _cos, _tan, _radians = math.sin, math.cos, math.tan, math.radians
_asin, _acos, _atan = math.asin, math.acos, math.atan
_degrees, _log, _log10 = math.degrees, math.log, math.log10
_e, _pi = math.e, math.pi
_ops = {'+': (2, _add), '-': (2, _sub), '*': (2, _mul), '/': (2, _truediv),
        '**': (2, _pow), 'sin': (1, _sin), 'cos': (1, _cos), 'tan': (1, _tan),
        'asin': (1, _asin), 'acos': (1, _acos), 'atan': (1, _atan),
        'sqrt': (1, _sqrt), 'rad': (1, _radians), 'deg': (1, _degrees),
        'ln': (1, _log), 'log': (1, _log10)}
_okeys = tuple(_ops.keys())
_consts = {'e': _e, 'pi': _pi}
_ckeys = tuple(_consts.keys())

def postfix(expression):
    """
    Evaluate a postfix expression.

    Arguments:
        expression: The expression to evaluate. Should be a string or a
                    sequence of strings. In a string numbers and operators
                    should be separated by whitespace

    Returns:
        The result of the expression.
    """
    if isinstance(expression, str):
        expression = expression.split()
    stack = []
    for val in expression:
        if val in _okeys:
            n, op = _ops[val]
            if n > len(stack):
                raise ValueError('not enough data on the stack')
            args = stack[-n:]
            stack[-n:] = [op(*args)]
        elif val in _ckeys:
            stack.append(_consts[val])
        else:
            stack.append(float(val))
    return stack[-1]

用法:

 In [2]: from postfix import postfix

In [3]: postfix('1 2 + 7 /')
Out[3]: 0.42857142857142855

In [4]: 3/7
Out[4]: 0.42857142857142855

原文由 Roland Smith 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题