【问题标题】:Evaluating a mathematical expression without eval() on Python3 [duplicate]在 Python3 上评估没有 eval() 的数学表达式
【发布时间】:2016-12-16 02:04:46
【问题描述】:

我正在开发一个“复制粘贴计算器”,它可以检测复制到系统剪贴板的任何数学表达式,评估它们并将答案复制到准备粘贴的剪贴板。然而,虽然代码使用了 eval() 函数,但考虑到用户通常知道他们在复制什么,我并不十分担心。话虽如此,我想找到一种更好的方法,而不会给计算带来障碍(= 例如,消除计算乘法或指数的能力)。

这是我的代码的重要部分:

#! python3
import pyperclip, time

parsedict = {"×": "*",
             "÷": "/",
             "^": "**"} # Get rid of anything that cannot be evaluated

def stringparse(string): # Remove whitespace and replace unevaluateable objects
    a = string
    a = a.replace(" ", "")
    for i in a:
        if i in parsedict.keys():
            a = a.replace(i, parsedict[i])
    print(a)
    return a

def calculate(string):
    parsed = stringparse(string)
    ans = eval(parsed) # EVIL!!!
    print(ans)
    pyperclip.copy(str(ans))

def validcheck(string): # Check if the copied item is a math expression
    proof = 0
    for i in mathproof:
        if i in string:
            proof += 1
        elif "http" in string: #TODO: Create a better way of passing non-math copies
            proof = 0
            break
    if proof != 0:
        calculate(string)

def init(): # Ensure previous copies have no effect
    current = pyperclip.paste()
    new = current
    main(current, new)

def main(current, new):
    while True:
        new = pyperclip.paste()
        if new != current:
            validcheck(new)
            current = new
            pass
        else:
            time.sleep(1.0)
            pass

if __name__ == "__main__":
    init()

问:我应该使用什么来代替 eval() 来计算答案?

【问题讨论】:

    标签: python-3.x eval mathematical-expressions


    【解决方案1】:

    你应该使用ast.parse:

    import ast
    
    try:
        tree = ast.parse(expression, mode='eval')
    except SyntaxError:
        return    # not a Python expression
    if not all(isinstance(node, (ast.Expression,
            ast.UnaryOp, ast.unaryop,
            ast.BinOp, ast.operator,
            ast.Num)) for node in ast.walk(tree)):
        return    # not a mathematical expression (numbers and operators)
    result = eval(compile(tree, filename='', mode='eval'))
    

    请注意,为简单起见,这允许所有一元运算符(+-~not)以及算术和按位二元运算符(+-、@987654330 @, /, %, // **, <<, >>, &, |, ^) 但不是逻辑或比较运算符。 If 应该可以直接细化或扩展允许的运算符。

    【讨论】:

    • 为什么最后还是用eval呢?这不是使用eval的重点吗?
    • @Jean。我想这是一个根据提问者对问题的描述,而不是他直接要求的内容,向提问者提供他想要的东西的情况。使用 ast 来验证表达式 only 是否包含某些类型和运算符的白名单,然后再将其传递给 eval() 对我来说似乎很安全。
    【解决方案2】:

    不使用eval,你必须实现一个解析器,或者使用现有的包,比如simpleeval(我不是作者,还有其他的,但我已经成功地测试过了)

    一行,加上import:

    >>> from simpleeval import simpleeval
    >>> simpleeval.simple_eval("(45 + -45) + 34")
    34
    >>> simpleeval.simple_eval("(45 - 22*2) + 34**2")
    1157
    

    现在,如果我尝试通过导入模块来破解计算器:

    >>> simpleeval.simple_eval("import os")
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "K:\CODE\COTS\python\simpleeval\simpleeval.py", line 466, in simple_eval
        return s.eval(expr)
      File "K:\CODE\COTS\python\simpleeval\simpleeval.py", line 274, in eval
        return self._eval(ast.parse(expr.strip()).body[0].value)
    AttributeError: 'Import' object has no attribute 'value'
    

    抓住了!神秘的错误消息来自 simpleeval 可以评估您可以选择通过字典传递的变量。捕获AttributeError 异常以拦截格式错误的表达式。不需要eval

    【讨论】:

      【解决方案3】:

      原生 Python3:不使用内置函数

      input_string = '1+1-1*4+1'
      result = 0
      counter = -1
      for ch in range(len(input_string)):
          if counter == ch:
              continue
          if input_string[ch] in ['-', '+', '/', '*', '**']:
              next_value = int(input_string[ch+1])
              if input_string[ch] == '-':
                  result -= next_value
                  counter = ch+1
              elif input_string[ch] == '+':
                  result += next_value
                  counter = ch+1
              elif input_string[ch] == '*':
                  result *= next_value
                  counter = ch+1
              elif input_string[ch] == '/':
                  result /= next_value
                  counter = ch+1
              elif input_string[ch] == '**':
                  result **= next_value
                  counter = ch+1
          else:
              result = int(input_string[ch])
      
      print(result)
      
      输出:
      
      原始字符串是:'1+1-1*4+1'
      评估结果为:5
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-12-05
        • 1970-01-01
        • 1970-01-01
        • 2011-06-30
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多