Python - оценка математического выражения в строке

У меня есть вопрос, касающийся оценки математического выражения в строке. Например, моя строка выглядит следующим образом:

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

Мне интересно, как оценить эту строку и получить следующий результат:

'I have 30 apples'

Есть ли способ сделать это?

Заранее спасибо.

PS питона eval функция не помогает в этом случае. Возникла ошибка при попытке оценить с помощью eval функция.

7 ответов

Вот моя попытка:

>>> 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'

Заметки:

Это очень просто, оно не будет иметь дело со сложными уравнениями - например, с квадратным корнем, пи и т. Д., Но я считаю, что это в духе того, что за вопросом. Для действительно надежного ответа см. Вопрос, отправленный jeffery_the_wind; но я считаю, что это может быть излишним для этого упрощенного случая.

Иногда лучше упростить вопрос, чем придумывать сложные решения. Вы можете упростить проблему, предоставив свой код следующим образом

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

Таким образом, вы можете разобрать его с помощью простого регулярного выражения и оценить, что внутри. В противном случае вас ждет много сложностей.

Это очень сложная проблема, которую, вероятно, почти невозможно решить вообще. Тем не менее, вот простой способ решения проблемы, который работает с примером ввода.

* шаг 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))

Используйте f-струны или нарезку.

F-струна: f'I have {str(6*(2+3))} apples'

Спасибо всем за вашу помощь. На самом деле мой приведенный пример очень прост по сравнению с тем, что я имею в реальной задаче. Я читаю эти строки из файла, и иногда это может выглядеть так:

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

Мой вариант:

>>> 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'

Для решения без использования 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]

Затем оцените выражения, используя NumericStringParser Класс из этого вопроса, который использует 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'

[Я знаю, что это старый вопрос, но стоит указать новые полезные решения, когда они появятся]

Начиная с python3.6, эта возможность теперь встроена в язык, придуманный "f-strings".

См.: PEP 498 - Интерполяция буквенных строк

Например (обратите внимание на 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'
Другие вопросы по тегам