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