Diapolo10
Diapolo10

Reputation: 336

Evaluating a mathematical expression without eval() on Python3

I'm working on a "copy-paste calculator" that detects any mathematical expressions copied to the system clipboard, evaluates them and copies the answer to the clipboard ready to be pasted. However, while the code uses the eval()-function, I'm not terribly concerned considering the user normally knows what they are copying. That being said, I want to find a better way without giving the calculations a handicap (= eg. removing the ability to calculate multiplications or exponents).

Here's the important parts of my code:

#! 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()

Q: What should I use instead of eval() to calculate the answer?

Upvotes: 7

Views: 11567

Answers (3)

Naveen
Naveen

Reputation: 1

By native Python3: without using inbuilt function

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

Upvotes: -5

Jean-François Fabre
Jean-François Fabre

Reputation: 140266

without using eval, you'd have to implement a parser, or use existing packages like simpleeval (I'm not the author, and there are others, but I have tested that one successfully)

In one line, plus import:

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

now if I try to hack the calculator by trying to import a module:

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

Caught! the cryptic error message comes from the fact that simpleeval can evaluate variables that you can optionally pass through a dictionary. Catch AttributeError exception to intercept wrongly formed expressions. No need for eval for that.

Upvotes: 3

ecatmur
ecatmur

Reputation: 157424

You should use 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'))

Note that for simplicity this allows all the unary operators (+, -, ~, not) as well as the arithmetic and bitwise binary operators (+, -, *, /, %, // **, <<, >>, &, |, ^) but not the logical or comparison operators. If should be straightforward to refine or expand the allowed operators.

Upvotes: 7

Related Questions