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?
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With