JasoonS
JasoonS

Reputation: 1462

Executing mathematical user inputed statements as code in Python 3

How would one allow the user to input a statement such as "math.sin(x)**2" and calculate the answer within python code.

In telling me the answer please explain why both exec() compile() are not producing the desired result.

import math

def getValue(function, x):
    function = "val = " + function
    #compile(function, '', 'exec')
    exec(function)
    print(val)

function = input("Enter a function f(x):\n")
getValue(function, 10)

Much Appreciated!

Upvotes: 2

Views: 115

Answers (1)

dawg
dawg

Reputation: 103754

To answer your question, use eval:

>>> eval('math.sin(1)**2')
0.7080734182735712

exec is working, but you are not retrieving the result. Notice:

>>> val
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'val' is not defined
>>> exec('val=math.sin(1)**2')
>>> val
0.7080734182735712

So, using eval instead of exec works like so:

def getValue(function, x):
    function = "{}({})".format(function, x)
    print(function)
    val=eval(function)
    print(val)

That said -- it is considered an extreme security risk to execute arbitrary user code.

If you are building a calculator, a safer approach you might consider using SymPy or building your own parser using something like PyParsing (which is used in SymPy)

An example PyParsing calculator:

import sys
import operator
from pyparsing import nums, oneOf, Word, Literal, Suppress
from pyparsing import ParseException, Forward, Group

op_map = { '*' : operator.mul,\
           '+' : operator.add,\
           '/' : operator.div,\
           '-' : operator.sub}

exp = Forward()

number = Word(nums).setParseAction(lambda s, l, t: int(t[0]))
lparen = Literal('(').suppress()
rparen = Literal(')').suppress()
op = oneOf('+ - * /').setResultsName('op').setParseAction(lambda s, l, t: op_map[t[0]])

exp << Group(lparen + op + (number | exp) + (number | exp) + rparen)

def processArg(arg):
    if isinstance(arg, int):
        return arg
    else:
        return processList(arg)

def processList(lst):
    args = [processArg(x) for x in lst[1:]]
    return lst.op(args[0], args[1])


def handleLine(line):
    result = exp.parseString(line)
    return processList(result[0])

while True:
    try:
        print handleLine(raw_input('> '))
    except ParseException, e:
        print >>sys.stderr,\
              "Syntax error at position %d: %s" % (e.col, e.line)
    except ZeroDivisionError:
        print >>sys.stderr,\
              "Division by zero error"

Which can easily be extended to include other functions.

Upvotes: 4

Related Questions