Safe Way to Parse User-Supplied Mathematical Formula in Python

Safe way to parse user-supplied mathematical formula in Python

Check out Paul McGuire's pyparsing. He has written both the general parser and a grammar for arithmetic expressions:

from __future__ import division
import pyparsing as pyp
import math
import operator

class NumericStringParser(object):
'''
Most of this code comes from the fourFn.py pyparsing example
http://pyparsing.wikispaces.com/file/view/fourFn.py
http://pyparsing.wikispaces.com/message/view/home/15549426
__author__='Paul McGuire'

All I've done is rewrap Paul McGuire's fourFn.py as a class, so I can use it
more easily in other places.
'''
def pushFirst(self, strg, loc, toks ):
self.exprStack.append( toks[0] )
def pushUMinus(self, strg, loc, toks ):
if toks and toks[0] == '-':
self.exprStack.append( 'unary -' )
def __init__(self):
"""
expop :: '^'
multop :: '*' | '/'
addop :: '+' | '-'
integer :: ['+' | '-'] '0'..'9'+
atom :: PI | E | real | fn '(' expr ')' | '(' expr ')'
factor :: atom [ expop factor ]*
term :: factor [ multop factor ]*
expr :: term [ addop term ]*
"""
point = pyp.Literal( "." )
e = pyp.CaselessLiteral( "E" )
fnumber = pyp.Combine( pyp.Word( "+-"+pyp.nums, pyp.nums ) +
pyp.Optional( point + pyp.Optional( pyp.Word( pyp.nums ) ) ) +
pyp.Optional( e + pyp.Word( "+-"+pyp.nums, pyp.nums ) ) )
ident = pyp.Word(pyp.alphas, pyp.alphas+pyp.nums+"_$")
plus = pyp.Literal( "+" )
minus = pyp.Literal( "-" )
mult = pyp.Literal( "*" )
div = pyp.Literal( "/" )
lpar = pyp.Literal( "(" ).suppress()
rpar = pyp.Literal( ")" ).suppress()
addop = plus | minus
multop = mult | div
expop = pyp.Literal( "^" )
pi = pyp.CaselessLiteral( "PI" )
expr = pyp.Forward()
atom = ((pyp.Optional(pyp.oneOf("- +")) +
(pi|e|fnumber|ident+lpar+expr+rpar).setParseAction(self.pushFirst))
| pyp.Optional(pyp.oneOf("- +")) + pyp.Group(lpar+expr+rpar)
).setParseAction(self.pushUMinus)
# by defining exponentiation as "atom [ ^ factor ]..." instead of
# "atom [ ^ atom ]...", we get right-to-left exponents, instead of left-to-right
# that is, 2^3^2 = 2^(3^2), not (2^3)^2.
factor = pyp.Forward()
factor << atom + pyp.ZeroOrMore( ( expop + factor ).setParseAction(
self.pushFirst ) )
term = factor + pyp.ZeroOrMore( ( multop + factor ).setParseAction(
self.pushFirst ) )
expr << term + pyp.ZeroOrMore( ( addop + term ).setParseAction( self.pushFirst ) )
self.bnf = expr
# map operator symbols to corresponding arithmetic operations
epsilon = 1e-12
self.opn = { "+" : operator.add,
"-" : operator.sub,
"*" : operator.mul,
"/" : operator.truediv,
"^" : operator.pow }
self.fn = { "sin" : math.sin,
"cos" : math.cos,
"tan" : math.tan,
"abs" : abs,
"trunc" : lambda a: int(a),
"round" : round,
# For Python3 compatibility, cmp replaced by ((a > 0) - (a < 0)). See
# https://docs.python.org/3.0/whatsnew/3.0.html#ordering-comparisons
"sgn" : lambda a: abs(a)>epsilon and ((a > 0) - (a < 0)) or 0}
self.exprStack = []
def evaluateStack(self, s ):
op = s.pop()
if op == 'unary -':
return -self.evaluateStack( s )
if op in "+-*/^":
op2 = self.evaluateStack( s )
op1 = self.evaluateStack( s )
return self.opn[op]( op1, op2 )
elif op == "PI":
return math.pi # 3.1415926535
elif op == "E":
return math.e # 2.718281828
elif op in self.fn:
return self.fn[op]( self.evaluateStack( s ) )
elif op[0].isalpha():
return 0
else:
return float( op )
def eval(self, num_string, parseAll = True):
self.exprStack = []
results = self.bnf.parseString(num_string, parseAll)
val = self.evaluateStack( self.exprStack[:] )
return val

nsp = NumericStringParser()
print(nsp.eval('1+2'))
# 3.0

print(nsp.eval('2*3-5'))
# 1.0

Equation parsing in Python

Python's own internal compiler can parse this, if you use Python notation.

If your change the notation slightly, you'll be happier.

import compiler
eq= "sin(x)*x**2"
ast= compiler.parse( eq )

You get an abstract syntax tree that you can work with.

Evaluating a mathematical expression (function) for a large number of input values fast

Since you asked about asteval, there is a way to use it and get faster results:

aeval = Interpreter()
time_start = time.time()
expr = aeval.parse(userinput_function)
for item in database_xy:
aeval.symtable['x'] = item['x']
item['y_aeval'] = aeval.run(expr)
time_end = time.time()

That is, you can first parse ("pre-compile") the user input function, and then insert each new value of x into the symbol table and the use Interpreter.run() to evaluate the compiled expression for that value. On your scale, I think this will get you close to 0.5 seconds.

If you are willing to use numpy, a hybrid solution:

aeval = Interpreter()
time_start = time.time()
expr = aeval.parse(userinput_function)
x = numpy.array([item['x'] for item in database_xy])
aeval.symtable['x'] = x
y = aeval.run(expr)
time_end = time.time()

should be much faster, and comparable in run time to using numexpr.

How to evaluate a mathematical expression string the user passed to the program in python?

You probably want the sympify function.

You can also use the split() function to split the string the user types into an array, in order to get the other necessary arguments you mention needing.

For example:

from sympy import sympify
def evaluate(x, args[]):
# do stuff
return answer
in = input("Enter your expression: ")
x = in.split(",")
print(evaluate(x[0], [x[1], x[2], ...]))

EDIT: Just realized I forgot to describe how to use sympify, which is probably the most important thing with this question. Here is a simple example that should get across how to use it:

x = sympify("x**2")

parsing math expression in python and solving to find an answer

If I wasn't going to rely on external libraries, I'd do it something like this:

def parse(x):
operators = set('+-*/')
op_out = [] #This holds the operators that are found in the string (left to right)
num_out = [] #this holds the non-operators that are found in the string (left to right)
buff = []
for c in x: #examine 1 character at a time
if c in operators:
#found an operator. Everything we've accumulated in `buff` is
#a single "number". Join it together and put it in `num_out`.
num_out.append(''.join(buff))
buff = []
op_out.append(c)
else:
#not an operator. Just accumulate this character in buff.
buff.append(c)
num_out.append(''.join(buff))
return num_out,op_out

print parse('3/2*15')

It's not the most elegant, but it gets you the pieces in a reasonable data structure (as far as I'm concerned anyway)

Now code to actually parse and evaluate the numbers -- This will do everything in floating point, but would be easy enough to change ...

import operator
def my_eval(nums,ops):

nums = list(nums)
ops = list(ops)
operator_order = ('*/','+-') #precedence from left to right. operators at same index have same precendece.
#map operators to functions.
op_dict = {'*':operator.mul,
'/':operator.div,
'+':operator.add,
'-':operator.sub}
Value = None
for op in operator_order: #Loop over precedence levels
while any(o in ops for o in op): #Operator with this precedence level exists
idx,oo = next((i,o) for i,o in enumerate(ops) if o in op) #Next operator with this precedence
ops.pop(idx) #remove this operator from the operator list
values = map(float,nums[idx:idx+2]) #here I just assume float for everything
value = op_dict[oo](*values)
nums[idx:idx+2] = [value] #clear out those indices

return nums[0]

print my_eval(*parse('3/2*15'))

Safe expression parser in Python

The examples provided with Pyparsing include several expression parsers:

  • https://github.com/pyparsing/pyparsing/blob/master/examples/fourFn.py is a conventional arithmetic infix notation parser/evaluator implementation using pyparsing. (Despite its name, this actually does 5-function arithmetic, plus several trig functions.)

  • https://github.com/pyparsing/pyparsing/blob/master/examples/simpleBool.py is a boolean infix notation parser/evaluator, using a pyparsing helper method operatorPrecedence, which simplifies the definition of infix operator notations.

  • https://github.com/pyparsing/pyparsing/blob/master/examples/simpleArith.py and https://github.com/pyparsing/pyparsing/blob/master/examples/eval_arith.py recast fourFn.py using operatorPrecedence. The first just parses and returns a parse tree; the second adds evaluation logic.

If you want a more pre-packaged solution, look at plusminus, a pyparsing-based extensible arithmetic parsing package.

Safely evaluate simple string equation

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.

Evaluate math equations from unsafe user input in Python

There is a relatively easy of doing this in Python without third party packages.

  • Using compile() to prepare a single-line Python expression to be bytecode for eval()

  • Not running the bytecode through eval(), but instead run it in your custom opcode loop and only implement opcodes which you really need. E.g. no built-ins, no attribute access, so the sandbox cannot escaped.

However there are some gotchas, like preparing for CPU exhaustion and memory exhaustion, which are not specific to this method and are issue on other approaches too.

Here is a full blog post about the topic. Here is a related gist. Below is shortened sample code.

""""

The orignal author: Alexer / #python.fi

"""

import opcode
import dis
import sys
import multiprocessing
import time

# Python 3 required
assert sys.version_info[0] == 3, "No country for old snakes"

class UnknownSymbol(Exception):
""" There was a function or constant in the expression we don't support. """

class BadValue(Exception):
""" The user tried to input dangerously big value. """

MAX_ALLOWED_VALUE = 2**63

class BadCompilingInput(Exception):
""" The user tried to input something which might cause compiler to slow down. """

def disassemble(co):
""" Loop through Python bytecode and match instructions with our internal opcodes.

:param co: Python code object
"""
code = co.co_code
n = len(code)
i = 0
extended_arg = 0
result = []
while i < n:
op = code[i]

curi = i
i = i+1
if op >= dis.HAVE_ARGUMENT:
# Python 2
# oparg = ord(code[i]) + ord(code[i+1])*256 + extended_arg
oparg = code[i] + code[i+1] * 256 + extended_arg
extended_arg = 0
i = i+2
if op == dis.EXTENDED_ARG:
# Python 2
#extended_arg = oparg*65536L
extended_arg = oparg*65536
else:
oparg = None

# print(opcode.opname[op])

opv = globals()[opcode.opname[op].replace('+', '_')](co, curi, i, op, oparg)

result.append(opv)

return result

# For the opcodes see dis.py
# (Copy-paste)
# https://docs.python.org/2/library/dis.html

class Opcode:
""" Base class for out internal opcodes. """
args = 0
pops = 0
pushes = 0
def __init__(self, co, i, nexti, op, oparg):
self.co = co
self.i = i
self.nexti = nexti
self.op = op
self.oparg = oparg

def get_pops(self):
return self.pops

def get_pushes(self):
return self.pushes

def touch_value(self, stack, frame):
assert self.pushes == 0
for i in range(self.pops):
stack.pop()

class OpcodeArg(Opcode):
args = 1

class OpcodeConst(OpcodeArg):
def get_arg(self):
return self.co.co_consts[self.oparg]

class OpcodeName(OpcodeArg):
def get_arg(self):
return self.co.co_names[self.oparg]

class POP_TOP(Opcode):
"""Removes the top-of-stack (TOS) item."""
pops = 1
def touch_value(self, stack, frame):
stack.pop()

class DUP_TOP(Opcode):
"""Duplicates the reference on top of the stack."""
# XXX: +-1
pops = 1
pushes = 2
def touch_value(self, stack, frame):
stack[-1:] = 2 * stack[-1:]

class ROT_TWO(Opcode):
"""Swaps the two top-most stack items."""
pops = 2
pushes = 2
def touch_value(self, stack, frame):
stack[-2:] = stack[-2:][::-1]

class ROT_THREE(Opcode):
"""Lifts second and third stack item one position up, moves top down to position three."""
pops = 3
pushes = 3
direct = True
def touch_value(self, stack, frame):
v3, v2, v1 = stack[-3:]
stack[-3:] = [v1, v3, v2]

class ROT_FOUR(Opcode):
"""Lifts second, third and forth stack item one position up, moves top down to position four."""
pops = 4
pushes = 4
direct = True
def touch_value(self, stack, frame):
v4, v3, v2, v1 = stack[-3:]
stack[-3:] = [v1, v4, v3, v2]

class UNARY(Opcode):
"""Unary Operations take the top of the stack, apply the operation, and push the result back on the stack."""
pops = 1
pushes = 1

class UNARY_POSITIVE(UNARY):
"""Implements TOS = +TOS."""
def touch_value(self, stack, frame):
stack[-1] = +stack[-1]

class UNARY_NEGATIVE(UNARY):
"""Implements TOS = -TOS."""
def touch_value(self, stack, frame):
stack[-1] = -stack[-1]

class BINARY(Opcode):
"""Binary operations remove the top of the stack (TOS) and the second top-most stack item (TOS1) from the stack. They perform the operation, and put the result back on the stack."""
pops = 2
pushes = 1

class BINARY_POWER(BINARY):
"""Implements TOS = TOS1 ** TOS."""
def touch_value(self, stack, frame):
TOS1, TOS = stack[-2:]
print(TOS1, TOS)
if abs(TOS1) > BadValue.MAX_ALLOWED_VALUE or abs(TOS) > BadValue.MAX_ALLOWED_VALUE:
raise BadValue("The value for exponent was too big")

stack[-2:] = [TOS1 ** TOS]

class BINARY_MULTIPLY(BINARY):
"""Implements TOS = TOS1 * TOS."""
def touch_value(self, stack, frame):
TOS1, TOS = stack[-2:]
stack[-2:] = [TOS1 * TOS]

class BINARY_DIVIDE(BINARY):
"""Implements TOS = TOS1 / TOS when from __future__ import division is not in effect."""
def touch_value(self, stack, frame):
TOS1, TOS = stack[-2:]
stack[-2:] = [TOS1 / TOS]

class BINARY_MODULO(BINARY):
"""Implements TOS = TOS1 % TOS."""
def touch_value(self, stack, frame):
TOS1, TOS = stack[-2:]
stack[-2:] = [TOS1 % TOS]

class BINARY_ADD(BINARY):
"""Implements TOS = TOS1 + TOS."""
def touch_value(self, stack, frame):
TOS1, TOS = stack[-2:]
stack[-2:] = [TOS1 + TOS]

class BINARY_SUBTRACT(BINARY):
"""Implements TOS = TOS1 - TOS."""
def touch_value(self, stack, frame):
TOS1, TOS = stack[-2:]
stack[-2:] = [TOS1 - TOS]

class BINARY_FLOOR_DIVIDE(BINARY):
"""Implements TOS = TOS1 // TOS."""
def touch_value(self, stack, frame):
TOS1, TOS = stack[-2:]
stack[-2:] = [TOS1 // TOS]

class BINARY_TRUE_DIVIDE(BINARY):
"""Implements TOS = TOS1 / TOS when from __future__ import division is in effect."""
def touch_value(self, stack, frame):
TOS1, TOS = stack[-2:]
stack[-2:] = [TOS1 / TOS]

class BINARY_LSHIFT(BINARY):
"""Implements TOS = TOS1 << TOS."""
def touch_value(self, stack, frame):
TOS1, TOS = stack[-2:]
stack[-2:] = [TOS1 << TOS]

class BINARY_RSHIFT(BINARY):
"""Implements TOS = TOS1 >> TOS."""
def touch_value(self, stack, frame):
TOS1, TOS = stack[-2:]
stack[-2:] = [TOS1 >> TOS]

class BINARY_AND(BINARY):
"""Implements TOS = TOS1 & TOS."""
def touch_value(self, stack, frame):
TOS1, TOS = stack[-2:]
stack[-2:] = [TOS1 & TOS]

class BINARY_XOR(BINARY):
"""Implements TOS = TOS1 ^ TOS."""
def touch_value(self, stack, frame):
TOS1, TOS = stack[-2:]
stack[-2:] = [TOS1 ^ TOS]

class BINARY_OR(BINARY):
"""Implements TOS = TOS1 | TOS."""
def touch_value(self, stack, frame):
TOS1, TOS = stack[-2:]
stack[-2:] = [TOS1 | TOS]

class RETURN_VALUE(Opcode):
"""Returns with TOS to the caller of the function."""
pops = 1
final = True
def touch_value(self, stack, frame):
value = stack.pop()
return value

class LOAD_CONST(OpcodeConst):
"""Pushes co_consts[consti] onto the stack.""" # consti
pushes = 1
def touch_value(self, stack, frame):
# XXX moo: Validate type
value = self.get_arg()
assert isinstance(value, (int, float))
stack.append(value)

class LOAD_NAME(OpcodeName):
"""Pushes the value associated with co_names[namei] onto the stack.""" # namei
pushes = 1
def touch_value(self, stack, frame):
# XXX moo: Get name from dict of valid variables/functions
name = self.get_arg()
if name not in frame:
raise UnknownSymbol("Does not know symbol {}".format(name))
stack.append(frame[name])

class CALL_FUNCTION(OpcodeArg):
"""Calls a function. The low byte of argc indicates the number of positional parameters, the high byte the number of keyword parameters. On the stack, the opcode finds the keyword parameters first. For each keyword argument, the value is on top of the key. Below the keyword parameters, the positional parameters are on the stack, with the right-most parameter on top. Below the parameters, the function object to call is on the stack. Pops all function arguments, and the function itself off the stack, and pushes the return value.""" # argc
pops = None
pushes = 1

def get_pops(self):
args = self.oparg & 0xff
kwargs = (self.oparg >> 8) & 0xff
return 1 + args + 2 * kwargs

def touch_value(self, stack, frame):
argc = self.oparg & 0xff
kwargc = (self.oparg >> 8) & 0xff
assert kwargc == 0
if argc > 0:
args = stack[-argc:]
stack[:] = stack[:-argc]
else:
args = []
func = stack.pop()

assert func in frame.values(), "Uh-oh somebody injected bad function. This does not happen."

result = func(*args)
stack.append(result)

def check_for_pow(expr):
""" Python evaluates power operator during the compile time if its on constants.

You can do CPU / memory burning attack with ``2**999999999999999999999**9999999999999``.
We mainly care about memory now, as we catch timeoutting in any case.

We just disable pow and do not care about it.
"""
if "**" in expr:
raise BadCompilingInput("Power operation is not allowed")

def _safe_eval(expr, functions_and_constants={}, check_compiling_input=True):
""" Evaluate a Pythonic math expression and return the output as a string.

The expr is limited to 1024 characters / 1024 operations
to prevent CPU burning or memory stealing.

:param functions_and_constants: Supplied "built-in" data for evaluation
"""

# Some safety checks
assert len(expr) < 1024

# Check for potential bad compiler input
if check_compiling_input:
check_for_pow(expr)

# Compile Python source code to Python code for eval()
code = compile(expr, '', 'eval')

# Dissect bytecode back to Python opcodes
ops = disassemble(code)
assert len(ops) < 1024

stack = []
for op in ops:
value = op.touch_value(stack, functions_and_constants)

return value

Evaluating a mathematical expression in a string

Pyparsing can be used to parse mathematical expressions. In particular, fourFn.py
shows how to parse basic arithmetic expressions. Below, I've rewrapped fourFn into a numeric parser class for easier reuse.

from __future__ import division
from pyparsing import (Literal, CaselessLiteral, Word, Combine, Group, Optional,
ZeroOrMore, Forward, nums, alphas, oneOf)
import math
import operator

__author__ = 'Paul McGuire'
__version__ = '$Revision: 0.0 $'
__date__ = '$Date: 2009-03-20 $'
__source__ = '''http://pyparsing.wikispaces.com/file/view/fourFn.py
http://pyparsing.wikispaces.com/message/view/home/15549426
'''
__note__ = '''
All I've done is rewrap Paul McGuire's fourFn.py as a class, so I can use it
more easily in other places.
'''

class NumericStringParser(object):
'''
Most of this code comes from the fourFn.py pyparsing example

'''

def pushFirst(self, strg, loc, toks):
self.exprStack.append(toks[0])

def pushUMinus(self, strg, loc, toks):
if toks and toks[0] == '-':
self.exprStack.append('unary -')

def __init__(self):
"""
expop :: '^'
multop :: '*' | '/'
addop :: '+' | '-'
integer :: ['+' | '-'] '0'..'9'+
atom :: PI | E | real | fn '(' expr ')' | '(' expr ')'
factor :: atom [ expop factor ]*
term :: factor [ multop factor ]*
expr :: term [ addop term ]*
"""
point = Literal(".")
e = CaselessLiteral("E")
fnumber = Combine(Word("+-" + nums, nums) +
Optional(point + Optional(Word(nums))) +
Optional(e + Word("+-" + nums, nums)))
ident = Word(alphas, alphas + nums + "_$")
plus = Literal("+")
minus = Literal("-")
mult = Literal("*")
div = Literal("/")
lpar = Literal("(").suppress()
rpar = Literal(")").suppress()
addop = plus | minus
multop = mult | div
expop = Literal("^")
pi = CaselessLiteral("PI")
expr = Forward()
atom = ((Optional(oneOf("- +")) +
(ident + lpar + expr + rpar | pi | e | fnumber).setParseAction(self.pushFirst))
| Optional(oneOf("- +")) + Group(lpar + expr + rpar)
).setParseAction(self.pushUMinus)
# by defining exponentiation as "atom [ ^ factor ]..." instead of
# "atom [ ^ atom ]...", we get right-to-left exponents, instead of left-to-right
# that is, 2^3^2 = 2^(3^2), not (2^3)^2.
factor = Forward()
factor << atom + \
ZeroOrMore((expop + factor).setParseAction(self.pushFirst))
term = factor + \
ZeroOrMore((multop + factor).setParseAction(self.pushFirst))
expr << term + \
ZeroOrMore((addop + term).setParseAction(self.pushFirst))
# addop_term = ( addop + term ).setParseAction( self.pushFirst )
# general_term = term + ZeroOrMore( addop_term ) | OneOrMore( addop_term)
# expr << general_term
self.bnf = expr
# map operator symbols to corresponding arithmetic operations
epsilon = 1e-12
self.opn = {"+": operator.add,
"-": operator.sub,
"*": operator.mul,
"/": operator.truediv,
"^": operator.pow}
self.fn = {"sin": math.sin,
"cos": math.cos,
"tan": math.tan,
"exp": math.exp,
"abs": abs,
"trunc": lambda a: int(a),
"round": round,
"sgn": lambda a: abs(a) > epsilon and cmp(a, 0) or 0}

def evaluateStack(self, s):
op = s.pop()
if op == 'unary -':
return -self.evaluateStack(s)
if op in "+-*/^":
op2 = self.evaluateStack(s)
op1 = self.evaluateStack(s)
return self.opn[op](op1, op2)
elif op == "PI":
return math.pi # 3.1415926535
elif op == "E":
return math.e # 2.718281828
elif op in self.fn:
return self.fn[op](self.evaluateStack(s))
elif op[0].isalpha():
return 0
else:
return float(op)

def eval(self, num_string, parseAll=True):
self.exprStack = []
results = self.bnf.parseString(num_string, parseAll)
val = self.evaluateStack(self.exprStack[:])
return val

You can use it like this

nsp = NumericStringParser()
result = nsp.eval('2^4')
print(result)
# 16.0

result = nsp.eval('exp(2^4)')
print(result)
# 8886110.520507872


Related Topics



Leave a reply



Submit