For the sake of argument I want to demonstrate that you don't have to use eval
at all for your problem! As PM 2Ring has warned, using eval
or exec
is a security hazard in case there's any chance that the string you're evaluating can be tampered with by malicious third parties. Many people dismiss this security risk, but if you read my favourite answer in the subject you can see a strikingly simple example to executing arbitrary programs outside python with a call to eval
. So one should never use eval
/exec
on sources that are not 100% trusted and under control.
Coming back to your problem, your examples make it seem that the pattern is always val1 OP val2
, i.e. two numbers (possibly negative, possibly floats) with an infix mathematical operator. This structure is so simple that you can (and I believe should) write your own parser for it. This serves as a demonstration that by explicitly handling inputs we expect in a safe way we can guarantee that our program breaks on invalid and malicious input.
Here's a function that returns the result for one line (one expression) of your input:
import operator
def evaluate(expr):
'''Evaluate expressions of the form `val1 OP val2`; OP is in [+,-,/,*,%,^]'''
ops = {
'+': operator.add,
'-': operator.sub,
'*': operator.mul,
'/': operator.truediv,
'%': operator.mod,
'^': operator.pow,
}
# gather operators and put minus last so we can handle negative numbers right
opskeys = list(ops)
ind = opskeys.index('-')
implemented_ops = opskeys[ind+1:] + opskeys[:ind+1]
for opnow in implemented_ops:
if opnow in expr:
val1,op,val2 = expr.rpartition(opnow)
# convert the numbers to floats, or ints in case they are integral
val1,val2 = (int(val) if val.is_integer() else val
for val in map(float,[val1,val2]))
break
else:
raise ValueError('Invalid input line "{}"!'.format(expr))
return ops[op](val1,val2) # or round(...,digits) see PM 2Ring's answer
The function takes a single line of input, finds the appropriate mathematical operator, splits the line using that character, then evaluates the result. It uses a dispatch dict to choose actual mathematical operators based on the characters; one upside is that we don't even have to convert the power operator. We convert integer inputs to integers, and leave the rest as floats.
The function handles subtraction last, this ensures that negative numbers don't mess up calculations with every other operator. This isn't entirely foolproof: in case there's an invalid line with something unexpected and negative numbers, we're probably going to get a ValueError
from the first conversion of val1
/val2
to float
. It might also break when subtracting a number from a negative number, or similar corner cases. Further safeguards (such as catching exceptions coming from float()
) could be added to the code; my point is to prove that you can very easily use a safe and controlled approach instead of eval
.
An example run with your data (hat tip again to PM 2Ring) plus an intentionally invalid input line:
data = '''\
2 + 2
3 * 3
4.5 - 2.3
-8.8 + 1.2
2 ^ 4
8.9 / 2.3
5 % 3
-2 * -2
3 @ 4
'''.splitlines()
for line in data:
print(evaluate(line))
The corresponding output in an interactive shell:
4
9
2.2
-7.6000000000000005
16
3.8695652173913047
2
4
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "foo.py", line 26, in evaluate
raise ValueError('Invalid input line "{}"!'.format(expr))
ValueError: Invalid input line "3 @ 4"!