I’m writing a program in which an equation is inputted as a string, then evaluated. So far, I’ve come up with this:
test_24_string = str(input("Enter your answer: "))
test_24 = eval(test_24_string)
I need both a string version of this equation and an evaluated version. However, eval is a very dangerous function. Using int() doesn’t work, though, because it’s an equation. Is there a Python function that will evaluate a mathematical expression from a string, as if inputting a number?
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
One way would be to use numexpr. It’s mostly a module for optimizing (and multithreading) numpy operations but it can also handle mathematical python expressions:
>>> import numexpr
>>> numexpr.evaluate('2 + 4.1 * 3')
array(14.299999999999999)
You can call .item on the result to get a python-like type:
>>> numexpr.evaluate('17 / 3').item()
5.666666666666667
It’s a 3rd party extension module so it may be total overkill here but it’s definetly safer than eval and supports quite a number of functions (including numpy and math operations). If also supports “variable substitution”:
>>> b = 10
>>> numexpr.evaluate('exp(17) / b').item()
2415495.27535753
One way with the python standard library, although very limited is ast.literal_eval. It works for the most basic data types and literals in Python:
>>> import ast
>>> ast.literal_eval('1+2')
3
But fails with more complicated expressions like:
>>> ast.literal_eval('import os')
SyntaxError: invalid syntax
>>> ast.literal_eval('exec(1+2)')
ValueError: malformed node or string: <_ast.Call object at 0x0000023BDEADB400>
Unfortunatly any operator besides + and - isn’t possible:
>>> ast.literal_eval('1.2 * 2.3')
ValueError: malformed node or string: <_ast.BinOp object at 0x0000023BDEF24B70>
I copied part of the documentation here that contains the supported types:
Safely evaluate an expression node or a string containing a Python literal or container display. The string or node provided may only consist of the following Python literal structures: strings, bytes, numbers, tuples, lists, dicts, sets, booleans, and None.
Method 2
It is not that difficult to write a postfix expression evaluator. Below is a working example. (Also available on github.)
import operator
import math
_add, _sub, _mul = operator.add, operator.sub, operator.mul
_truediv, _pow, _sqrt = operator.truediv, operator.pow, math.sqrt
_sin, _cos, _tan, _radians = math.sin, math.cos, math.tan, math.radians
_asin, _acos, _atan = math.asin, math.acos, math.atan
_degrees, _log, _log10 = math.degrees, math.log, math.log10
_e, _pi = math.e, math.pi
_ops = {'+': (2, _add), '-': (2, _sub), '*': (2, _mul), '/': (2, _truediv),
'**': (2, _pow), 'sin': (1, _sin), 'cos': (1, _cos), 'tan': (1, _tan),
'asin': (1, _asin), 'acos': (1, _acos), 'atan': (1, _atan),
'sqrt': (1, _sqrt), 'rad': (1, _radians), 'deg': (1, _degrees),
'ln': (1, _log), 'log': (1, _log10)}
_okeys = tuple(_ops.keys())
_consts = {'e': _e, 'pi': _pi}
_ckeys = tuple(_consts.keys())
def postfix(expression):
"""
Evaluate a postfix expression.
Arguments:
expression: The expression to evaluate. Should be a string or a
sequence of strings. In a string numbers and operators
should be separated by whitespace
Returns:
The result of the expression.
"""
if isinstance(expression, str):
expression = expression.split()
stack = []
for val in expression:
if val in _okeys:
n, op = _ops[val]
if n > len(stack):
raise ValueError('not enough data on the stack')
args = stack[-n:]
stack[-n:] = [op(*args)]
elif val in _ckeys:
stack.append(_consts[val])
else:
stack.append(float(val))
return stack[-1]
Usage:
In [2]: from postfix import postfix
In [3]: postfix('1 2 + 7 /')
Out[3]: 0.42857142857142855
In [4]: 3/7
Out[4]: 0.42857142857142855
Method 3
I did this for me needs to answer the same question. It is easy to adapt.
import math
import ast
import operator as op
class MathParser:
""" Basic parser with local variable and math functions
Args:
vars (mapping): mapping object where obj[name] -> numerical value
math (bool, optional): if True (default) all math function are added in the same name space
Example:
data = {'r': 3.4, 'theta': 3.141592653589793}
parser = MathParser(data)
assert parser.parse('r*cos(theta)') == -3.4
data['theta'] =0.0
assert parser.parse('r*cos(theta)') == 3.4
"""
_operators2method = {
ast.Add: op.add,
ast.Sub: op.sub,
ast.BitXor: op.xor,
ast.Or: op.or_,
ast.And: op.and_,
ast.Mod: op.mod,
ast.Mult: op.mul,
ast.Div: op.truediv,
ast.Pow: op.pow,
ast.FloorDiv: op.floordiv,
ast.USub: op.neg,
ast.UAdd: lambda a:a
}
def __init__(self, vars, math=True):
self._vars = vars
if not math:
self._alt_name = self._no_alt_name
def _Name(self, name):
try:
return self._vars[name]
except KeyError:
return self._alt_name(name)
@staticmethod
def _alt_name(name):
if name.startswith("_"):
raise NameError(f"{name!r}")
try:
return getattr(math, name)
except AttributeError:
raise NameError(f"{name!r}")
@staticmethod
def _no_alt_name(name):
raise NameError(f"{name!r}")
def eval_(self, node):
if isinstance(node, ast.Expression):
return self.eval_(node.body)
if isinstance(node, ast.Num): # <number>
return node.n
if isinstance(node, ast.Name):
return self._Name(node.id)
if isinstance(node, ast.BinOp):
method = self._operators2method[type(node.op)]
return method( self.eval_(node.left), self.eval_(node.right) )
if isinstance(node, ast.UnaryOp):
method = self._operators2method[type(node.op)]
return method( self.eval_(node.operand) )
if isinstance(node, ast.Attribute):
return getattr(self.eval_(node.value), node.attr)
if isinstance(node, ast.Call):
return self.eval_(node.func)(
*(self.eval_(a) for a in node.args),
**{k.arg:self.eval_(k.value) for k in node.keywords}
)
return self.Call( self.eval_(node.func), tuple(self.eval_(a) for a in node.args))
else:
raise TypeError(node)
def parse(self, expr):
return self.eval_(ast.parse(expr, mode='eval'))
Test & Usage
assert MathParser({"x":4.5}).parse('x*2') == 9
assert MathParser({}).parse('cos(pi)') == -1.0
data = {'r': 3.4, 'theta': 3.141592653589793}
parser = MathParser(data)
assert parser.parse('r*cos(theta)') == -3.4
data['theta'] = 0.0
assert parser.parse('r*cos(theta)') == 3.4
assert MathParser(globals()).parse('math.pi') == math.pi
assert MathParser({'f':lambda x,n=10: x*n}).parse('f(2,20)') == 40
assert MathParser({'f':lambda x,n=10: x*n}).parse('f(2,n=20)') == 40
Method 4
I had the same problem and settled with this:
def safe_math_eval(string):
allowed_chars = "0123456789+-*(). /"
for char in string:
if char not in allowed_chars:
raise Exception("Unsafe eval")
return eval(string)
There could still be a security issue in there which I can’t see.
If there is an security issue please tell me.
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0