Вычисление математического выражения без eval() на Python3

Я работаю над «калькулятором копирования-вставки», который обнаруживает любые математические выражения, скопированные в системный буфер обмена, оценивает их и копирует ответ в буфер обмена, готовый для вставки. Однако, хотя в коде используется функция 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() для вычисления ответа?


person Diapolo10    schedule 09.08.2016    source источник


Ответы (3)


Вы должны использовать 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), а также арифметические и побитовые бинарные операторы (+, -, *, /, %, // **, <<, >>, &, |, ^), но не логические операторы или операторы сравнения. Если должно быть просто уточнить или расширить разрешенные операторы.

person ecatmur    schedule 09.08.2016
comment
почему это, наконец, использует eval в конце? разве не в этом смысл не использовать eval ? - person Jean-François Fabre; 27.03.2018
comment
@Джин. Я предполагаю, что это тот случай, когда спрашивающему дается то, что он хочет, основываясь на его описании проблемы, а не на том, о чем он прямо просил. Использование ast для проверки того, что выражение only содержит белый список определенных типов и операторов перед передачей его в eval(), кажется мне довольно безопасным. - person Dave Rove; 23.03.2020
comment
есть хороший пакет simpleeval, который выполняет вычисления без использования eval. У вас была возможность это проверить? Это очень хорошо. Вероятно, он тоже использует синтаксический анализатор, но не использует eval - person Jean-François Fabre; 23.03.2020

без использования eval вам пришлось бы реализовать синтаксический анализатор или использовать существующие пакеты, такие как simpleeval (я я не автор, есть и другие, но этот успешно протестировал)

В одной строке плюс импорт:

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

person Jean-François Fabre    schedule 23.03.2020

Нативным 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)
Output : 

The original string is : '1+1-1*4+1'
The evaluated result is : 5
person Naveen    schedule 04.03.2021