【问题标题】:Python - Evaluate math expression within string [duplicate]Python - 评估字符串中的数学表达式[重复]
【发布时间】:2012-08-28 16:03:25
【问题描述】:

我有一个关于字符串中数学表达式求值的问题。 例如我的字符串如下:

my_str='I have 6 * (2 + 3) apples'

我想知道如何评估这个字符串并得到以下结果:

'I have 30 apples'

有什么办法吗?

提前致谢。

附:在这种情况下,python 的 eval 函数没有帮助。尝试使用 eval 函数进行评估时引发错误。

【问题讨论】:

标签: python string math expression eval


【解决方案1】:

使用 f 字符串或切片。

F字串:f'I have {str(6*(2+3))} apples'

【讨论】:

    【解决方案2】:

    这是一个非常棘手的问题,一般来说几乎不可能解决。然而,这里有一个简单的方法来解决与示例输入一起工作的问题。

    *step 1 -- 清理输入。一般来说,这是最难做的部分。基本上,您需要一种方法来从字符串中提取单个数学表达式,而不会破坏它。这里可以使用一个简单的正则表达式:

    sanitized = re.sub(r'[a-zA-Z]','',my_str).strip()
    

    *第 2 步 -- 使用eval 评估:

    value = eval(sanitized, {'__builtins__':None})
    

    *第 3 步——后退替换

    new_string = my_str.replace(sanitized, str(value))
    

    【讨论】:

    • 嗯,不是一般情况下解决不了的。这是一个定义不明确的问题(什么构成要评估的表达式,什么不是?)但是一旦我们确定了这一点,它就很容易解决,如果你真的费心去解析东西而不是滥用eval
    • @delnan -- 如果这是eval 滥用,那么什么不是eval 滥用(此时,它不应该从语言中完全删除吗?)。我认为这是一个使用eval 的完全好地方,因为问题受到足够的约束,可以从输入字符串中解析要评估的表达式。
    • 我个人认为 eval 不应该放在显眼的位置(即在全局命名空间中)。我知道compileexec 的用例(它们与此有很大不同,因为它们100% 控制输入字符串并且知道它将做什么)。我还没有遇到eval 的一个很好的用例——当你想评估一个数学表达式时,写一个分流场评估器或其他东西。如果您想运行 Python 代码,请使用 exec,因为它的限制较少。为了正确性,我不会避免eval,而是不要将代码与数据混淆。 (把那些说代码就是数据的 lisp 人搞砸了。)
    • @delnan -- 编写自己的调车场算法?真的吗?为什么要通过所有这些工作来创建和调试标准库中已经(突出显示)的东西?我理解你不知道你会得到什么的论点,但由于鸭子类型和运算符重载,python 中的 any function 在某种程度上是正确的。道德是把好的输入放到你的函数中,你会得到好的东西。另请注意,此处'6 * ( 2 + 3 )' 不是代码。这是一个字符串。我们不会试图用这个或任何东西来改变程序流程......
    • 退后一步,我的主要观点是许多有用的关于应该评估什么的定义需要更多的解析(参见关于何时 reStructuredText 中的 * 只是一个字符而不是标记的规则构造)。是的,6 * ( 2 + 3 ) 是一个字符串——不是 Python 表达式。这正是我反对将其传递给eval 的原因。此外,考虑到它的详细描述(我自己做了),SY 实现起来并不难,而且很容易测试。对于这样的功能或大多数其他功能可能发生的事情有一个合理的限制。对于 eval 等,唯一的限制是 Python。
    【解决方案3】:

    [我知道这是一个老问题,但值得指出新的有用解决方案,因为它们弹出]

    从 python3.6 开始,此功能现在内置于语言中,创造了 “f-strings”

    请参阅:PEP 498 -- Literal String Interpolation

    例如(注意 f 前缀):

    f'I have {6 * (2 + 3)} apples'
    => 'I have 30 apples'
    color = 'green'
    f'I have {6 * (2 + 3)} {color} apples'
    => 'I have 30 green apples'
    

    【讨论】:

      【解决方案4】:

      这是我的尝试:

      >>> import string
      >>> s = 'I have 6 * (2+3) apples'
      >>> symbols = '^*()/+-'
      >>> formula = [(x,s.index(x)) for x in s if x in string.digits+symbols]
      >>> result = eval(''.join(x[0] for x in formula), {'__builtins__':None})
      >>> s = s[:formula[0][1]] + str(result) + s[formula[-1][1]+1:]
      >>> s
      'I have 30 apples'
      

      注意事项:

      这很简单,它不会处理复杂的方程——比如那些有平方根、pi 等的方程,但我相信它符合问题所在的精神。对于真正可靠的答案,请参阅question posted by jeffery_the_wind;但我认为对于这种简单化的案例来说,这可能是矫枉过正。

      【讨论】:

        【解决方案5】:

        有时最好简化问题,而不是提出复杂的解决方案。您可能希望通过像这样提供代码来简化问题

        my_str='I have {6 * (2 + 3)} apples'
        

        通过这种方式,您可以使用简单的正则表达式解析它并评估里面的内容。否则,您将面临很多复杂性。

        【讨论】:

          【解决方案6】:

          感谢大家的帮助。实际上,与我在实际任务中的示例相比,我提供的示例非常简单。我从文件中读取了这些字符串,有时可以看到这样的视图:

          my_str='ENC M6_finger_VNCAPa (AA SYZE BY (0.14*2)) < (0.12 + 0.07) OPPOSITE REGION'
          

          数学方程式很简单,但可以在一个字符串中多次出现,应单独计算。

          所以我编写了一个示例代码,它能够处理这种情况: 也许不是那么好,但解决问题:

          def eval_math_expressions(filelist):
                  for line in filelist:
                        if re.match('.*[\-|\+|\*|\/].*',line):
                                  lindex=int(filelist.index(line))
                                  line_list=line.split()
                                  exp=[]
                                  for word in line_list:
                                          if re.match('^\(+\d+',word) or re.match('^[\)+|\d+|\-|\+|\*|\/]',word):
                                                  exp.append(word)
                                          else:
                                                  ready=' '.join(exp)
                                                  if ready:
                                                          eval_ready=str(eval(ready))
                                                          line_new=line.replace(ready,eval_ready)
                                                          line=line_new
                                                          filelist[lindex]=line
                                                  exp=[]
                  return filelist
          

          【讨论】:

            【解决方案7】:

            对于不使用 eval 的解决方案,我会这样做。首先查找字符串中的所有数学表达式,我将其定义为包含空格、括号、数字和运算的字符串,然后去除所有为空格的匹配项:

            >>> import re
            >>> my_str = 'I have 6 * (2 + 3) apples'
            >>> exprs = list(re.finditer(r"[\d\.\s\*\+\-\/\(\)]+", my_str))
            >>> exprs = [e for e in exprs if len(my_str[e.start():e.end()].strip()) > 0]
            

            接下来,使用来自this questionNumericStringParser 类评估表达式,该类使用pyparsing

            >>> nsp = NumericStringParser()
            >>> results = [nsp.eval(my_str[e.start():e.end()]) for e in exprs]
            >>> results
            [30.0]
            

            然后,要将结果替换回表达式,请按表达式的起始索引对表达式进行反向排序并将它们放回原始字符串中:

            >>> new_str = my_str
            >>> for expr, res in sorted(zip(exprs, results), key=lambda t: t[0].start(), reverse=True):
            ...     new_str = new_str[:expr.start()] + (" %d " % res) + new_str[expr.end():]
            ... 
            >>> new_str
            'I have 30 apples'
            

            【讨论】:

              【解决方案8】:

              我的选择:

              >>> import re
              >>> def calc(s):
              ...     val = s.group()
              ...     if not val.strip(): return val
              ...     return " %s " % eval(val.strip(), {'__builtins__': None})
              >>> re.sub(r"([0-9\ \.\+\*\-\/(\)]+)", calc, "I have 6 * (2 + 3 ) apples")
              'I have 30 apples'
              

              【讨论】:

                猜你喜欢
                • 2012-05-29
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2022-01-17
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多